From 1da7647981bb2c00bb1cc0075f4b1a66d4aa4d52 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Thu, 13 Mar 2025 09:39:14 -0400 Subject: [PATCH 01/14] Define a map template method for single value --- .../sql/AbstractSingleValueCondition.java | 11 ++++++ .../org/mybatis/dynamic/sql/SqlBuilder.java | 17 +++++---- .../CaseInsensitiveRenderableCondition.java | 4 +- .../sql/where/condition/IsEqualTo.java | 10 +---- .../sql/where/condition/IsGreaterThan.java | 10 +---- .../condition/IsGreaterThanOrEqualTo.java | 10 +---- .../where/condition/IsInCaseInsensitive.java | 2 +- .../IsInCaseInsensitiveWhenPresent.java | 2 +- .../sql/where/condition/IsLessThan.java | 10 +---- .../where/condition/IsLessThanOrEqualTo.java | 10 +---- .../dynamic/sql/where/condition/IsLike.java | 10 +---- .../condition/IsLikeCaseInsensitive.java | 33 +++++++--------- .../sql/where/condition/IsNotEqualTo.java | 10 +---- .../condition/IsNotInCaseInsensitive.java | 2 +- .../IsNotInCaseInsensitiveWhenPresent.java | 2 +- .../sql/where/condition/IsNotLike.java | 13 +------ .../condition/IsNotLikeCaseInsensitive.java | 24 ++++++------ .../sql/util/kotlin/elements/SqlElements.kt | 8 ++-- .../java/examples/mysql/IsLikeEscape.java | 1 + .../sql/where/condition/FilterAndMapTest.java | 38 +++++++++---------- .../kotlin/mybatis3/mariadb/KIsLikeEscape.kt | 2 +- 21 files changed, 86 insertions(+), 143 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java index 9c8248d02..f032d64db 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java @@ -64,6 +64,17 @@ protected > S mapSupport(Function filter(Predicate predicate); + /** + * 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 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. + */ + public abstract AbstractSingleValueCondition map(Function mapper); + public abstract String operator(); @Override diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index 1e27db93d..8d462c698 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -882,35 +882,36 @@ static IsEqualTo isFalse() { } // conditions for strings only - static IsLikeCaseInsensitive isLikeCaseInsensitive(String value) { + static IsLikeCaseInsensitive isLikeCaseInsensitive(String value) { return IsLikeCaseInsensitive.of(value); } - static IsLikeCaseInsensitive isLikeCaseInsensitive(Supplier valueSupplier) { + static IsLikeCaseInsensitive isLikeCaseInsensitive(Supplier valueSupplier) { return isLikeCaseInsensitive(valueSupplier.get()); } - static IsLikeCaseInsensitive isLikeCaseInsensitiveWhenPresent(@Nullable String value) { + static IsLikeCaseInsensitive isLikeCaseInsensitiveWhenPresent(@Nullable String value) { return value == null ? IsLikeCaseInsensitive.empty() : IsLikeCaseInsensitive.of(value); } - static IsLikeCaseInsensitive isLikeCaseInsensitiveWhenPresent(Supplier<@Nullable String> valueSupplier) { + static IsLikeCaseInsensitive isLikeCaseInsensitiveWhenPresent(Supplier<@Nullable String> valueSupplier) { return isLikeCaseInsensitiveWhenPresent(valueSupplier.get()); } - static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(String value) { + static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(String value) { return IsNotLikeCaseInsensitive.of(value); } - static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(Supplier valueSupplier) { + static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(Supplier valueSupplier) { return isNotLikeCaseInsensitive(valueSupplier.get()); } - static IsNotLikeCaseInsensitive isNotLikeCaseInsensitiveWhenPresent(@Nullable String value) { + static IsNotLikeCaseInsensitive isNotLikeCaseInsensitiveWhenPresent(@Nullable String value) { return value == null ? IsNotLikeCaseInsensitive.empty() : IsNotLikeCaseInsensitive.of(value); } - static IsNotLikeCaseInsensitive isNotLikeCaseInsensitiveWhenPresent(Supplier<@Nullable String> valueSupplier) { + static IsNotLikeCaseInsensitive isNotLikeCaseInsensitiveWhenPresent( + Supplier<@Nullable String> valueSupplier) { return isNotLikeCaseInsensitiveWhenPresent(valueSupplier.get()); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java index 2c837c4c7..977a0090b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java @@ -20,11 +20,11 @@ import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; -public interface CaseInsensitiveRenderableCondition extends RenderableCondition { +public interface CaseInsensitiveRenderableCondition extends RenderableCondition { @Override default FragmentAndParameters renderLeftColumn(RenderingContext renderingContext, - BindableColumn leftColumn) { + BindableColumn leftColumn) { return RenderableCondition.super.renderLeftColumn(renderingContext, leftColumn) .mapFragment(s -> "upper(" + s + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java index 0a4b19207..17fe9eef1 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java @@ -59,15 +59,7 @@ public IsEqualTo filter(Predicate predicate) { return filterSupport(predicate, IsEqualTo::empty, this); } - /** - * 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 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. - */ + @Override public IsEqualTo map(Function mapper) { return mapSupport(mapper, IsEqualTo::new, IsEqualTo::empty); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java index a0ed1a2bc..2b3162e1a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java @@ -58,15 +58,7 @@ public IsGreaterThan filter(Predicate predicate) { return filterSupport(predicate, IsGreaterThan::empty, this); } - /** - * 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 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. - */ + @Override public IsGreaterThan map(Function mapper) { return mapSupport(mapper, IsGreaterThan::new, IsGreaterThan::empty); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java index 59560339b..7439ae4a8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java @@ -58,15 +58,7 @@ public IsGreaterThanOrEqualTo filter(Predicate predicate) { return filterSupport(predicate, IsGreaterThanOrEqualTo::empty, this); } - /** - * 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 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. - */ + @Override public IsGreaterThanOrEqualTo map(Function mapper) { return mapSupport(mapper, IsGreaterThanOrEqualTo::new, IsGreaterThanOrEqualTo::empty); } 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 67f37951e..8fdf172bd 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 @@ -27,7 +27,7 @@ import org.mybatis.dynamic.sql.util.Validator; public class IsInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsInCaseInsensitive EMPTY = new IsInCaseInsensitive(Collections.emptyList()); public static IsInCaseInsensitive empty() { 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 cff58415d..91b77901a 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 @@ -27,7 +27,7 @@ import org.mybatis.dynamic.sql.util.Utilities; public class IsInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsInCaseInsensitiveWhenPresent EMPTY = new IsInCaseInsensitiveWhenPresent(Collections.emptyList()); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java index 09e2e7ba6..01614d6dc 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java @@ -58,15 +58,7 @@ public IsLessThan filter(Predicate predicate) { return filterSupport(predicate, IsLessThan::empty, this); } - /** - * 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 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. - */ + @Override public IsLessThan map(Function mapper) { return mapSupport(mapper, IsLessThan::new, IsLessThan::empty); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java index b11d06c88..20c3ed460 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java @@ -58,15 +58,7 @@ public IsLessThanOrEqualTo filter(Predicate predicate) { return filterSupport(predicate, IsLessThanOrEqualTo::empty, this); } - /** - * 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 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. - */ + @Override public IsLessThanOrEqualTo map(Function mapper) { return mapSupport(mapper, IsLessThanOrEqualTo::new, IsLessThanOrEqualTo::empty); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java index 9d79bed81..5c7ba967a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java @@ -58,15 +58,7 @@ public IsLike filter(Predicate predicate) { return filterSupport(predicate, IsLike::empty, this); } - /** - * 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 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. - */ + @Override public IsLike map(Function mapper) { return mapSupport(mapper, IsLike::new, IsLike::empty); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java index 4ebdb00fb..921903d01 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java @@ -16,15 +16,15 @@ package org.mybatis.dynamic.sql.where.condition; import java.util.NoSuchElementException; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; import org.mybatis.dynamic.sql.util.StringUtilities; -public class IsLikeCaseInsensitive extends AbstractSingleValueCondition - implements CaseInsensitiveRenderableCondition { - private static final IsLikeCaseInsensitive EMPTY = new IsLikeCaseInsensitive("") { //$NON-NLS-1$ +public class IsLikeCaseInsensitive extends AbstractSingleValueCondition + implements CaseInsensitiveRenderableCondition { + private static final IsLikeCaseInsensitive EMPTY = new IsLikeCaseInsensitive<>("") { //$NON-NLS-1$ @Override public String value() { throw new NoSuchElementException("No value present"); //$NON-NLS-1$ @@ -36,11 +36,13 @@ public boolean isEmpty() { } }; - public static IsLikeCaseInsensitive empty() { - return EMPTY; + public static IsLikeCaseInsensitive empty() { + @SuppressWarnings("unchecked") + IsLikeCaseInsensitive t = (IsLikeCaseInsensitive) EMPTY; + return t; } - protected IsLikeCaseInsensitive(String value) { + protected IsLikeCaseInsensitive(T value) { super(value); } @@ -50,25 +52,18 @@ public String operator() { } @Override - public IsLikeCaseInsensitive filter(Predicate predicate) { + public IsLikeCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsLikeCaseInsensitive::empty, this); } - /** - * 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 - * @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. - */ - public IsLikeCaseInsensitive map(UnaryOperator mapper) { + @Override + public IsLikeCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsLikeCaseInsensitive::new, IsLikeCaseInsensitive::empty); } - public static IsLikeCaseInsensitive of(String value) { + public static IsLikeCaseInsensitive of(String value) { // Keep the null safe upper case utility for backwards compatibility //noinspection DataFlowIssue - return new IsLikeCaseInsensitive(value).map(StringUtilities::safelyUpperCase); + return new IsLikeCaseInsensitive<>(value).map(StringUtilities::safelyUpperCase); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java index 821fd019f..e52ab0385 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java @@ -58,15 +58,7 @@ public IsNotEqualTo filter(Predicate predicate) { return filterSupport(predicate, IsNotEqualTo::empty, this); } - /** - * 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 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. - */ + @Override public IsNotEqualTo map(Function mapper) { return mapSupport(mapper, IsNotEqualTo::new, IsNotEqualTo::empty); } 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 f5f970c45..473b9077e 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 @@ -27,7 +27,7 @@ import org.mybatis.dynamic.sql.util.Validator; public class IsNotInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsNotInCaseInsensitive EMPTY = new IsNotInCaseInsensitive(Collections.emptyList()); public static IsNotInCaseInsensitive empty() { 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 a8873a7e4..b2d834f2b 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 @@ -27,7 +27,7 @@ import org.mybatis.dynamic.sql.util.Utilities; public class IsNotInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsNotInCaseInsensitiveWhenPresent EMPTY = new IsNotInCaseInsensitiveWhenPresent(Collections.emptyList()); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java index 7379a316b..a2cf60310 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java @@ -58,18 +58,7 @@ public IsNotLike filter(Predicate predicate) { return filterSupport(predicate, IsNotLike::empty, this); } - /** - * 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 - * 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. - */ + @Override public IsNotLike map(Function mapper) { return mapSupport(mapper, IsNotLike::new, IsNotLike::empty); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java index 1fb6847c0..4b3604165 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java @@ -16,15 +16,15 @@ package org.mybatis.dynamic.sql.where.condition; import java.util.NoSuchElementException; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; import org.mybatis.dynamic.sql.util.StringUtilities; -public class IsNotLikeCaseInsensitive extends AbstractSingleValueCondition - implements CaseInsensitiveRenderableCondition { - private static final IsNotLikeCaseInsensitive EMPTY = new IsNotLikeCaseInsensitive("") { //$NON-NLS-1$ +public class IsNotLikeCaseInsensitive extends AbstractSingleValueCondition + implements CaseInsensitiveRenderableCondition { + private static final IsNotLikeCaseInsensitive EMPTY = new IsNotLikeCaseInsensitive<>("") { //$NON-NLS-1$ @Override public String value() { throw new NoSuchElementException("No value present"); //$NON-NLS-1$ @@ -36,11 +36,13 @@ public boolean isEmpty() { } }; - public static IsNotLikeCaseInsensitive empty() { - return EMPTY; + public static IsNotLikeCaseInsensitive empty() { + @SuppressWarnings("unchecked") + IsNotLikeCaseInsensitive t = (IsNotLikeCaseInsensitive) EMPTY; + return t; } - protected IsNotLikeCaseInsensitive(String value) { + protected IsNotLikeCaseInsensitive(T value) { super(value); } @@ -50,7 +52,7 @@ public String operator() { } @Override - public IsNotLikeCaseInsensitive filter(Predicate predicate) { + public IsNotLikeCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsNotLikeCaseInsensitive::empty, this); } @@ -64,13 +66,13 @@ public IsNotLikeCaseInsensitive filter(Predicate predicate) { * @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. */ - public IsNotLikeCaseInsensitive map(UnaryOperator mapper) { + public IsNotLikeCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsNotLikeCaseInsensitive::new, IsNotLikeCaseInsensitive::empty); } - public static IsNotLikeCaseInsensitive of(String value) { + public static IsNotLikeCaseInsensitive of(String value) { // Keep the null safe upper case utility for backwards compatibility //noinspection DataFlowIssue - return new IsNotLikeCaseInsensitive(value).map(StringUtilities::safelyUpperCase); + return new IsNotLikeCaseInsensitive<>(value).map(StringUtilities::safelyUpperCase); } } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt index 536032605..0983eaa9c 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt @@ -324,14 +324,14 @@ fun isTrue(): IsEqualTo = isEqualTo(true) fun isFalse(): IsEqualTo = isEqualTo(false) // conditions for strings only -fun isLikeCaseInsensitive(value: String): IsLikeCaseInsensitive = SqlBuilder.isLikeCaseInsensitive(value) +fun isLikeCaseInsensitive(value: String): IsLikeCaseInsensitive = SqlBuilder.isLikeCaseInsensitive(value) -fun isLikeCaseInsensitiveWhenPresent(value: String?): IsLikeCaseInsensitive = +fun isLikeCaseInsensitiveWhenPresent(value: String?): IsLikeCaseInsensitive = SqlBuilder.isLikeCaseInsensitiveWhenPresent(value) -fun isNotLikeCaseInsensitive(value: String): IsNotLikeCaseInsensitive = SqlBuilder.isNotLikeCaseInsensitive(value) +fun isNotLikeCaseInsensitive(value: String): IsNotLikeCaseInsensitive = SqlBuilder.isNotLikeCaseInsensitive(value) -fun isNotLikeCaseInsensitiveWhenPresent(value: String?): IsNotLikeCaseInsensitive = +fun isNotLikeCaseInsensitiveWhenPresent(value: String?): IsNotLikeCaseInsensitive = SqlBuilder.isNotLikeCaseInsensitiveWhenPresent(value) fun isInCaseInsensitive(vararg values: String): IsInCaseInsensitive = isInCaseInsensitive(values.asList()) diff --git a/src/test/java/examples/mysql/IsLikeEscape.java b/src/test/java/examples/mysql/IsLikeEscape.java index 19e1a085c..21b43fa13 100644 --- a/src/test/java/examples/mysql/IsLikeEscape.java +++ b/src/test/java/examples/mysql/IsLikeEscape.java @@ -77,6 +77,7 @@ public IsLikeEscape filter(Predicate predicate) { return filterSupport(predicate, IsLikeEscape::empty, this); } + @Override public IsLikeEscape map(Function mapper) { return mapSupport(mapper, v -> new IsLikeEscape<>(v, escapeCharacter), IsLikeEscape::empty); } diff --git a/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java b/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java index bd5f2e2cd..358ec0cd3 100644 --- a/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java @@ -28,14 +28,14 @@ class FilterAndMapTest { @Test void testTypeConversion() { - IsEqualTo cond = SqlBuilder.isEqualTo("1").map(Integer::parseInt); + var cond = SqlBuilder.isEqualTo("1").map(Integer::parseInt); assertThat(cond.isEmpty()).isFalse(); assertThat(cond.value()).isEqualTo(1); } @Test void testTypeConversionWithNullThrowsException() { - IsEqualTo cond = SqlBuilder.isEqualTo((String) null); + var cond = SqlBuilder.isEqualTo((String) null); assertThatExceptionOfType(NumberFormatException.class).isThrownBy(() -> cond.map(Integer::parseInt) ); @@ -43,7 +43,7 @@ void testTypeConversionWithNullThrowsException() { @Test void testTypeConversionWithNullAndFilterDoesNotThrowException() { - IsEqualTo cond = SqlBuilder.isEqualTo((String) null).filter(Objects::nonNull).map(Integer::parseInt); + var cond = SqlBuilder.isEqualTo((String) null).filter(Objects::nonNull).map(Integer::parseInt); assertThat(cond.isEmpty()).isTrue(); } @@ -328,8 +328,8 @@ void testIsLikeMapUnRenderableShouldNotThrowNullPointerException() { @Test void testIsLikeCaseInsensitiveRenderableTruePredicateShouldReturnSameObject() { - IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitive("Fred"); - IsLikeCaseInsensitive filtered = cond.filter(s -> true); + var cond = SqlBuilder.isLikeCaseInsensitive("Fred"); + var filtered = cond.filter(s -> true); assertThat(filtered.value()).isEqualTo("FRED"); assertThat(filtered.isEmpty()).isFalse(); assertThat(cond).isSameAs(filtered); @@ -337,24 +337,24 @@ void testIsLikeCaseInsensitiveRenderableTruePredicateShouldReturnSameObject() { @Test void testIsLikeCaseInsensitiveRenderableFalsePredicate() { - IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitive("Fred"); - IsLikeCaseInsensitive filtered = cond.filter(s -> false); + var cond = SqlBuilder.isLikeCaseInsensitive("Fred"); + var filtered = cond.filter(s -> false); assertThat(cond.isEmpty()).isFalse(); assertThat(filtered.isEmpty()).isTrue(); } @Test void testIsLikeCaseInsensitiveFilterUnRenderableShouldReturnSameObject() { - IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitive("Fred").filter(s -> false); - IsLikeCaseInsensitive filtered = cond.filter(s -> true); + var cond = SqlBuilder.isLikeCaseInsensitive("Fred").filter(s -> false); + var filtered = cond.filter(s -> true); assertThat(filtered.isEmpty()).isTrue(); assertThat(cond).isSameAs(filtered); } @Test void testIsLikeCaseInsensitiveMapUnRenderableShouldNotThrowNullPointerException() { - IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitive("Fred").filter(s -> false); - IsLikeCaseInsensitive mapped = cond.map(String::toUpperCase); + IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitive("Fred").filter(s -> false); + IsLikeCaseInsensitive mapped = cond.map(String::toUpperCase); assertThat(cond.isEmpty()).isTrue(); assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(cond::value); assertThat(cond).isSameAs(mapped); @@ -395,8 +395,8 @@ void testIsNotLikeMapUnRenderableShouldNotThrowNullPointerException() { @Test void testIsNotLikeCaseInsensitiveRenderableTruePredicateShouldReturnSameObject() { - IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitive("Fred"); - IsNotLikeCaseInsensitive filtered = cond.filter(s -> true); + var cond = SqlBuilder.isNotLikeCaseInsensitive("Fred"); + var filtered = cond.filter(s -> true); assertThat(filtered.value()).isEqualTo("FRED"); assertThat(filtered.isEmpty()).isFalse(); assertThat(cond).isSameAs(filtered); @@ -404,24 +404,24 @@ void testIsNotLikeCaseInsensitiveRenderableTruePredicateShouldReturnSameObject() @Test void testIsNotLikeCaseInsensitiveRenderableFalsePredicate() { - IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitive("Fred"); - IsNotLikeCaseInsensitive filtered = cond.filter(s -> false); + var cond = SqlBuilder.isNotLikeCaseInsensitive("Fred"); + var filtered = cond.filter(s -> false); assertThat(cond.isEmpty()).isFalse(); assertThat(filtered.isEmpty()).isTrue(); } @Test void testIsNotLikeCaseInsensitiveFilterUnRenderableShouldReturnSameObject() { - IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitive("Fred").filter(s -> false); - IsNotLikeCaseInsensitive filtered = cond.filter(s -> true); + var cond = SqlBuilder.isNotLikeCaseInsensitive("Fred").filter(s -> false); + var filtered = cond.filter(s -> true); assertThat(filtered.isEmpty()).isTrue(); assertThat(cond).isSameAs(filtered); } @Test void testIsNotLikeCaseInsensitiveMapUnRenderableShouldNotThrowNullPointerException() { - IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitive("Fred").filter(s -> false); - IsNotLikeCaseInsensitive mapped = cond.map(String::toUpperCase); + var cond = SqlBuilder.isNotLikeCaseInsensitive("Fred").filter(s -> false); + var mapped = cond.map(String::toUpperCase); assertThat(cond.isEmpty()).isTrue(); assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(cond::value); assertThat(cond).isSameAs(mapped); diff --git a/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt b/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt index e018a8329..06dca6609 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt @@ -24,7 +24,7 @@ sealed class KIsLikeEscape( override fun filter(predicate: Predicate): KIsLikeEscape = filterSupport(predicate, EmptyIsLikeEscape::empty, this) - fun map(mapper : Function): KIsLikeEscape = + override fun map(mapper : Function): KIsLikeEscape = mapSupport(mapper, { r -> ConcreteIsLikeEscape(r, escapeCharacter) }, EmptyIsLikeEscape::empty) companion object { From 1d41ea4ea587141498d3a66b8199e979391cf144 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Thu, 13 Mar 2025 09:48:23 -0400 Subject: [PATCH 02/14] Define a map template method for no value --- .../dynamic/sql/AbstractNoValueCondition.java | 16 +++++++++++++- .../sql/where/condition/IsNotNull.java | 12 +---------- .../dynamic/sql/where/condition/IsNull.java | 12 +---------- .../examples/mysql/MemberOfCondition.java | 21 +++++++++++++++++++ 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java index c0e103baf..f28073bf2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java @@ -34,7 +34,21 @@ protected > S filterSupport(BooleanSupplie public abstract String operator(); - @Override + /** + * 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 + * 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. + */ + public abstract AbstractNoValueCondition filter(BooleanSupplier booleanSupplier); + + + @Override public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn leftColumn) { return FragmentAndParameters.fromFragment(operator()); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java index 03d558387..fa13adbaf 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java @@ -42,17 +42,7 @@ public String operator() { return "is not null"; //$NON-NLS-1$ } - /** - * 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 - * 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. - */ + @Override public IsNotNull filter(BooleanSupplier booleanSupplier) { @SuppressWarnings("unchecked") IsNotNull self = (IsNotNull) this; diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java index 36c68aa34..befbb9a13 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java @@ -42,17 +42,7 @@ public String operator() { return "is null"; //$NON-NLS-1$ } - /** - * 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 - * 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. - */ + @Override public IsNull filter(BooleanSupplier booleanSupplier) { @SuppressWarnings("unchecked") IsNull self = (IsNull) this; diff --git a/src/test/java/examples/mysql/MemberOfCondition.java b/src/test/java/examples/mysql/MemberOfCondition.java index 33b967556..c805b2127 100644 --- a/src/test/java/examples/mysql/MemberOfCondition.java +++ b/src/test/java/examples/mysql/MemberOfCondition.java @@ -16,12 +16,26 @@ package examples.mysql; import java.util.Objects; +import java.util.function.BooleanSupplier; import org.jspecify.annotations.NullMarked; import org.mybatis.dynamic.sql.AbstractNoValueCondition; @NullMarked public class MemberOfCondition extends AbstractNoValueCondition { + private static final MemberOfCondition EMPTY = new MemberOfCondition<>("") { + @Override + public boolean isEmpty() { + return true; + } + }; + + public static MemberOfCondition empty() { + @SuppressWarnings("unchecked") + MemberOfCondition t = (MemberOfCondition) EMPTY; + return t; + } + private final String jsonArray; protected MemberOfCondition(String jsonArray) { @@ -33,6 +47,13 @@ public String operator() { return "member of(" + jsonArray + ")"; } + @Override + public MemberOfCondition filter(BooleanSupplier booleanSupplier) { + @SuppressWarnings("unchecked") + MemberOfCondition self = (MemberOfCondition) this; + return filterSupport(booleanSupplier, MemberOfCondition::empty, self); + } + public static MemberOfCondition memberOf(String jsonArray) { return new MemberOfCondition<>(jsonArray); } From e52fa81434a3dfd1d3b7b62451c6f2f55f066ed2 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Thu, 13 Mar 2025 10:03:42 -0400 Subject: [PATCH 03/14] Define a map template method for list value --- .../sql/AbstractListValueCondition.java | 10 ++++++ .../org/mybatis/dynamic/sql/SqlBuilder.java | 16 ++++----- .../dynamic/sql/where/condition/IsIn.java | 9 +---- .../where/condition/IsInCaseInsensitive.java | 34 ++++++++---------- .../IsInCaseInsensitiveWhenPresent.java | 36 +++++++++---------- .../dynamic/sql/where/condition/IsNotIn.java | 9 +---- .../condition/IsNotInCaseInsensitive.java | 34 ++++++++---------- .../IsNotInCaseInsensitiveWhenPresent.java | 36 +++++++++---------- .../sql/util/kotlin/elements/SqlElements.kt | 27 +++++++------- .../sql/where/condition/FilterAndMapTest.java | 16 ++++----- 10 files changed, 105 insertions(+), 122 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java index 23f48e6f4..066012e28 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java @@ -83,6 +83,16 @@ protected > S mapSupport(Function filter(Predicate predicate); + /** + * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. + * Else return an empty condition (this). + * + * @param mapper a mapping function to apply to the values, if not empty + * @param type of the new condition + * @return a new condition with mapped values if renderable, otherwise an empty condition + */ + public abstract AbstractListValueCondition map(Function mapper); + public abstract String operator(); @Override diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index 8d462c698..2f365de1e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -915,36 +915,36 @@ static IsNotLikeCaseInsensitive isNotLikeCaseInsensitiveWhenPresent( return isNotLikeCaseInsensitiveWhenPresent(valueSupplier.get()); } - static IsInCaseInsensitive isInCaseInsensitive(String... values) { + static IsInCaseInsensitive isInCaseInsensitive(String... values) { return IsInCaseInsensitive.of(values); } - static IsInCaseInsensitive isInCaseInsensitive(Collection values) { + static IsInCaseInsensitive isInCaseInsensitive(Collection values) { return IsInCaseInsensitive.of(values); } - static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent(@Nullable String... values) { + static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent(@Nullable String... values) { return IsInCaseInsensitiveWhenPresent.of(values); } - static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent( + static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent( @Nullable Collection<@Nullable String> values) { return values == null ? IsInCaseInsensitiveWhenPresent.empty() : IsInCaseInsensitiveWhenPresent.of(values); } - static IsNotInCaseInsensitive isNotInCaseInsensitive(String... values) { + static IsNotInCaseInsensitive isNotInCaseInsensitive(String... values) { return IsNotInCaseInsensitive.of(values); } - static IsNotInCaseInsensitive isNotInCaseInsensitive(Collection values) { + static IsNotInCaseInsensitive isNotInCaseInsensitive(Collection values) { return IsNotInCaseInsensitive.of(values); } - static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(@Nullable String... values) { + static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(@Nullable String... values) { return IsNotInCaseInsensitiveWhenPresent.of(values); } - static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent( + static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent( @Nullable Collection<@Nullable String> values) { return values == null ? IsNotInCaseInsensitiveWhenPresent.empty() : IsNotInCaseInsensitiveWhenPresent.of(values); 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 6ce403717..67b6fff08 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 @@ -54,14 +54,7 @@ public IsIn filter(Predicate predicate) { return filterSupport(predicate, IsIn::new, this, IsIn::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @param type of the new condition - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ + @Override public IsIn map(Function mapper) { Function, IsIn> constructor = IsIn::new; return mapSupport(mapper, constructor, IsIn::empty); 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 8fdf172bd..6f2313bdb 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 @@ -18,23 +18,25 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.StringUtilities; import org.mybatis.dynamic.sql.util.Validator; -public class IsInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { - private static final IsInCaseInsensitive EMPTY = new IsInCaseInsensitive(Collections.emptyList()); +public class IsInCaseInsensitive extends AbstractListValueCondition + implements CaseInsensitiveRenderableCondition { + private static final IsInCaseInsensitive EMPTY = new IsInCaseInsensitive<>(Collections.emptyList()); - public static IsInCaseInsensitive empty() { - return EMPTY; + public static IsInCaseInsensitive empty() { + @SuppressWarnings("unchecked") + IsInCaseInsensitive t = (IsInCaseInsensitive) EMPTY; + return t; } - protected IsInCaseInsensitive(Collection values) { + protected IsInCaseInsensitive(Collection values) { super(values); } @@ -50,28 +52,22 @@ public String operator() { } @Override - public IsInCaseInsensitive filter(Predicate predicate) { + public IsInCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsInCaseInsensitive::new, this, IsInCaseInsensitive::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsInCaseInsensitive map(UnaryOperator mapper) { + @Override + public IsInCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsInCaseInsensitive::new, IsInCaseInsensitive::empty); } - public static IsInCaseInsensitive of(String... values) { + public static IsInCaseInsensitive of(String... values) { return of(Arrays.asList(values)); } - public static IsInCaseInsensitive of(Collection values) { + public static IsInCaseInsensitive of(Collection values) { // Keep the null safe upper case utility for backwards compatibility //noinspection DataFlowIssue - return new IsInCaseInsensitive(values).map(StringUtilities::safelyUpperCase); + return new IsInCaseInsensitive<>(values).map(StringUtilities::safelyUpperCase); } } 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 91b77901a..523b3d732 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 @@ -18,24 +18,26 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.util.StringUtilities; import org.mybatis.dynamic.sql.util.Utilities; -public class IsInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { - private static final IsInCaseInsensitiveWhenPresent EMPTY = - new IsInCaseInsensitiveWhenPresent(Collections.emptyList()); +public class IsInCaseInsensitiveWhenPresent extends AbstractListValueCondition + implements CaseInsensitiveRenderableCondition { + private static final IsInCaseInsensitiveWhenPresent EMPTY = + new IsInCaseInsensitiveWhenPresent<>(Collections.emptyList()); - public static IsInCaseInsensitiveWhenPresent empty() { - return EMPTY; + public static IsInCaseInsensitiveWhenPresent empty() { + @SuppressWarnings("unchecked") + IsInCaseInsensitiveWhenPresent t = (IsInCaseInsensitiveWhenPresent) EMPTY; + return t; } - protected IsInCaseInsensitiveWhenPresent(Collection<@Nullable String> values) { + protected IsInCaseInsensitiveWhenPresent(Collection<@Nullable T> values) { super(Utilities.removeNullElements(values)); } @@ -45,29 +47,23 @@ public String operator() { } @Override - public IsInCaseInsensitiveWhenPresent filter(Predicate predicate) { + public IsInCaseInsensitiveWhenPresent filter(Predicate predicate) { return filterSupport(predicate, IsInCaseInsensitiveWhenPresent::new, this, IsInCaseInsensitiveWhenPresent::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsInCaseInsensitiveWhenPresent map(UnaryOperator mapper) { + @Override + public IsInCaseInsensitiveWhenPresent map(Function mapper) { return mapSupport(mapper, IsInCaseInsensitiveWhenPresent::new, IsInCaseInsensitiveWhenPresent::empty); } - public static IsInCaseInsensitiveWhenPresent of(@Nullable String... values) { + public static IsInCaseInsensitiveWhenPresent of(@Nullable String... values) { return of(Arrays.asList(values)); } - public static IsInCaseInsensitiveWhenPresent of(Collection<@Nullable String> values) { + public static IsInCaseInsensitiveWhenPresent of(Collection<@Nullable String> values) { // Keep the null safe upper case utility for backwards compatibility //noinspection DataFlowIssue - return new IsInCaseInsensitiveWhenPresent(values).map(StringUtilities::safelyUpperCase); + return new IsInCaseInsensitiveWhenPresent<>(values).map(StringUtilities::safelyUpperCase); } } 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 e1ca07abf..f22c504c8 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 @@ -54,14 +54,7 @@ public IsNotIn filter(Predicate predicate) { return filterSupport(predicate, IsNotIn::new, this, IsNotIn::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @param type of the new condition - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ + @Override public IsNotIn map(Function mapper) { Function, IsNotIn> constructor = IsNotIn::new; return mapSupport(mapper, constructor, IsNotIn::empty); 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 473b9077e..2687ef9b7 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 @@ -18,23 +18,25 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.StringUtilities; import org.mybatis.dynamic.sql.util.Validator; -public class IsNotInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { - private static final IsNotInCaseInsensitive EMPTY = new IsNotInCaseInsensitive(Collections.emptyList()); +public class IsNotInCaseInsensitive extends AbstractListValueCondition + implements CaseInsensitiveRenderableCondition { + private static final IsNotInCaseInsensitive EMPTY = new IsNotInCaseInsensitive<>(Collections.emptyList()); - public static IsNotInCaseInsensitive empty() { - return EMPTY; + public static IsNotInCaseInsensitive empty() { + @SuppressWarnings("unchecked") + IsNotInCaseInsensitive t = (IsNotInCaseInsensitive) EMPTY; + return t; } - protected IsNotInCaseInsensitive(Collection values) { + protected IsNotInCaseInsensitive(Collection values) { super(values); } @@ -50,28 +52,22 @@ public String operator() { } @Override - public IsNotInCaseInsensitive filter(Predicate predicate) { + public IsNotInCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsNotInCaseInsensitive::new, this, IsNotInCaseInsensitive::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsNotInCaseInsensitive map(UnaryOperator mapper) { + @Override + public IsNotInCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsNotInCaseInsensitive::new, IsNotInCaseInsensitive::empty); } - public static IsNotInCaseInsensitive of(String... values) { + public static IsNotInCaseInsensitive of(String... values) { return of(Arrays.asList(values)); } - public static IsNotInCaseInsensitive of(Collection values) { + public static IsNotInCaseInsensitive of(Collection values) { // Keep the null safe upper case utility for backwards compatibility //noinspection DataFlowIssue - return new IsNotInCaseInsensitive(values).map(StringUtilities::safelyUpperCase); + return new IsNotInCaseInsensitive<>(values).map(StringUtilities::safelyUpperCase); } } 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 b2d834f2b..143d2a6a2 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 @@ -18,24 +18,26 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.util.StringUtilities; import org.mybatis.dynamic.sql.util.Utilities; -public class IsNotInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { - private static final IsNotInCaseInsensitiveWhenPresent EMPTY = - new IsNotInCaseInsensitiveWhenPresent(Collections.emptyList()); +public class IsNotInCaseInsensitiveWhenPresent extends AbstractListValueCondition + implements CaseInsensitiveRenderableCondition { + private static final IsNotInCaseInsensitiveWhenPresent EMPTY = + new IsNotInCaseInsensitiveWhenPresent<>(Collections.emptyList()); - public static IsNotInCaseInsensitiveWhenPresent empty() { - return EMPTY; + public static IsNotInCaseInsensitiveWhenPresent empty() { + @SuppressWarnings("unchecked") + IsNotInCaseInsensitiveWhenPresent t = (IsNotInCaseInsensitiveWhenPresent) EMPTY; + return t; } - protected IsNotInCaseInsensitiveWhenPresent(Collection<@Nullable String> values) { + protected IsNotInCaseInsensitiveWhenPresent(Collection<@Nullable T> values) { super(Utilities.removeNullElements(values)); } @@ -45,29 +47,23 @@ public String operator() { } @Override - public IsNotInCaseInsensitiveWhenPresent filter(Predicate predicate) { + public IsNotInCaseInsensitiveWhenPresent filter(Predicate predicate) { return filterSupport(predicate, IsNotInCaseInsensitiveWhenPresent::new, this, IsNotInCaseInsensitiveWhenPresent::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsNotInCaseInsensitiveWhenPresent map(UnaryOperator mapper) { + @Override + public IsNotInCaseInsensitiveWhenPresent map(Function mapper) { return mapSupport(mapper, IsNotInCaseInsensitiveWhenPresent::new, IsNotInCaseInsensitiveWhenPresent::empty); } - public static IsNotInCaseInsensitiveWhenPresent of(@Nullable String... values) { + public static IsNotInCaseInsensitiveWhenPresent of(@Nullable String... values) { return of(Arrays.asList(values)); } - public static IsNotInCaseInsensitiveWhenPresent of(Collection<@Nullable String> values) { + public static IsNotInCaseInsensitiveWhenPresent of(Collection<@Nullable String> values) { // Keep the null safe upper case utility for backwards compatibility //noinspection DataFlowIssue - return new IsNotInCaseInsensitiveWhenPresent(values).map(StringUtilities::safelyUpperCase); + return new IsNotInCaseInsensitiveWhenPresent<>(values).map(StringUtilities::safelyUpperCase); } } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt index 0983eaa9c..530c962ed 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt @@ -334,40 +334,43 @@ fun isNotLikeCaseInsensitive(value: String): IsNotLikeCaseInsensitive = fun isNotLikeCaseInsensitiveWhenPresent(value: String?): IsNotLikeCaseInsensitive = SqlBuilder.isNotLikeCaseInsensitiveWhenPresent(value) -fun isInCaseInsensitive(vararg values: String): IsInCaseInsensitive = isInCaseInsensitive(values.asList()) +fun isInCaseInsensitive(vararg values: String): IsInCaseInsensitive = isInCaseInsensitive(values.asList()) @JvmName("isInArrayCaseInsensitive") -fun isInCaseInsensitive(values: Array): IsInCaseInsensitive = SqlBuilder.isInCaseInsensitive(values.asList()) +fun isInCaseInsensitive(values: Array): IsInCaseInsensitive = + SqlBuilder.isInCaseInsensitive(values.asList()) -fun isInCaseInsensitive(values: Collection): IsInCaseInsensitive = SqlBuilder.isInCaseInsensitive(values) +fun isInCaseInsensitive(values: Collection): IsInCaseInsensitive = + SqlBuilder.isInCaseInsensitive(values) -fun isInCaseInsensitiveWhenPresent(vararg values: String?): IsInCaseInsensitiveWhenPresent = +fun isInCaseInsensitiveWhenPresent(vararg values: String?): IsInCaseInsensitiveWhenPresent = isInCaseInsensitiveWhenPresent(values.asList()) @JvmName("isInArrayCaseInsensitiveWhenPresent") -fun isInCaseInsensitiveWhenPresent(values: Array?): IsInCaseInsensitiveWhenPresent = +fun isInCaseInsensitiveWhenPresent(values: Array?): IsInCaseInsensitiveWhenPresent = SqlBuilder.isInCaseInsensitiveWhenPresent(values?.asList()) -fun isInCaseInsensitiveWhenPresent(values: Collection?): IsInCaseInsensitiveWhenPresent = +fun isInCaseInsensitiveWhenPresent(values: Collection?): IsInCaseInsensitiveWhenPresent = SqlBuilder.isInCaseInsensitiveWhenPresent(values) -fun isNotInCaseInsensitive(vararg values: String): IsNotInCaseInsensitive = isNotInCaseInsensitive(values.asList()) +fun isNotInCaseInsensitive(vararg values: String): IsNotInCaseInsensitive = + isNotInCaseInsensitive(values.asList()) @JvmName("isNotInArrayCaseInsensitive") -fun isNotInCaseInsensitive(values: Array): IsNotInCaseInsensitive = +fun isNotInCaseInsensitive(values: Array): IsNotInCaseInsensitive = SqlBuilder.isNotInCaseInsensitive(values.asList()) -fun isNotInCaseInsensitive(values: Collection): IsNotInCaseInsensitive = +fun isNotInCaseInsensitive(values: Collection): IsNotInCaseInsensitive = SqlBuilder.isNotInCaseInsensitive(values) -fun isNotInCaseInsensitiveWhenPresent(vararg values: String?): IsNotInCaseInsensitiveWhenPresent = +fun isNotInCaseInsensitiveWhenPresent(vararg values: String?): IsNotInCaseInsensitiveWhenPresent = isNotInCaseInsensitiveWhenPresent(values.asList()) @JvmName("isNotInArrayCaseInsensitiveWhenPresent") -fun isNotInCaseInsensitiveWhenPresent(values: Array?): IsNotInCaseInsensitiveWhenPresent = +fun isNotInCaseInsensitiveWhenPresent(values: Array?): IsNotInCaseInsensitiveWhenPresent = SqlBuilder.isNotInCaseInsensitiveWhenPresent(values?.asList()) -fun isNotInCaseInsensitiveWhenPresent(values: Collection?): IsNotInCaseInsensitiveWhenPresent = +fun isNotInCaseInsensitiveWhenPresent(values: Collection?): IsNotInCaseInsensitiveWhenPresent = SqlBuilder.isNotInCaseInsensitiveWhenPresent(values) // order by support diff --git a/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java b/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java index 358ec0cd3..8c8f35eb5 100644 --- a/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java @@ -447,25 +447,25 @@ void testIsNotInRenderableMapShouldReturnMappedObject() { @Test void testIsNotInCaseInsensitiveRenderableMapShouldReturnMappedObject() { - IsNotInCaseInsensitive cond = SqlBuilder.isNotInCaseInsensitive("Fred ", "Wilma "); - List values = cond.values().toList(); + var cond = SqlBuilder.isNotInCaseInsensitive("Fred ", "Wilma "); + var values = cond.values().toList(); assertThat(values).containsExactly("FRED ", "WILMA "); assertThat(cond.isEmpty()).isFalse(); - IsNotInCaseInsensitive mapped = cond.map(String::trim); - List mappedValues = mapped.values().toList(); + var mapped = cond.map(String::trim); + var mappedValues = mapped.values().toList(); assertThat(mappedValues).containsExactly("FRED", "WILMA"); } @Test void testIsInCaseInsensitiveRenderableMapShouldReturnMappedObject() { - IsInCaseInsensitive cond = SqlBuilder.isInCaseInsensitive("Fred ", "Wilma "); - List values = cond.values().toList(); + var cond = SqlBuilder.isInCaseInsensitive("Fred ", "Wilma "); + var values = cond.values().toList(); assertThat(values).containsExactly("FRED ", "WILMA "); assertThat(cond.isEmpty()).isFalse(); - IsInCaseInsensitive mapped = cond.map(String::trim); - List mappedValues = mapped.values().toList(); + var mapped = cond.map(String::trim); + var mappedValues = mapped.values().toList(); assertThat(mappedValues).containsExactly("FRED", "WILMA"); } From d73b31b945be5f349cb907e8a78283b87cd14bc0 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Thu, 13 Mar 2025 10:10:28 -0400 Subject: [PATCH 04/14] Define a map template method for two value --- .../sql/AbstractTwoValueCondition.java | 24 +++++++++++++++++++ .../sql/where/condition/IsBetween.java | 21 ++-------------- .../sql/where/condition/IsNotBetween.java | 21 ++-------------- 3 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java index 865f7db0b..15a5b6ffe 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java @@ -88,6 +88,30 @@ protected > S mapSupport(Function filter(Predicate predicate); + /** + * 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 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. + */ + public abstract AbstractTwoValueCondition map(Function mapper1, + Function 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 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. + */ + public abstract AbstractTwoValueCondition map(Function mapper); + public abstract String operator1(); public abstract String operator2(); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java index deeddb4c9..0d554d0fc 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java @@ -71,29 +71,12 @@ public IsBetween filter(Predicate predicate) { return filterSupport(predicate, IsBetween::empty, this); } - /** - * 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 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. - */ + @Override public IsBetween map(Function mapper1, Function mapper2) { return mapSupport(mapper1, mapper2, IsBetween::new, IsBetween::empty); } - /** - * 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 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. - */ + @Override public IsBetween map(Function mapper) { return map(mapper, mapper); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java index 5700de2ac..f7eb98f52 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java @@ -71,30 +71,13 @@ public IsNotBetween filter(Predicate predicate) { return filterSupport(predicate, IsNotBetween::empty, this); } - /** - * 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 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. - */ + @Override public IsNotBetween map(Function mapper1, Function mapper2) { return mapSupport(mapper1, mapper2, IsNotBetween::new, IsNotBetween::empty); } - /** - * 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 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. - */ + @Override public IsNotBetween map(Function mapper) { return map(mapper, mapper); } From f49cf7f28f3f9a48ac8c880895f0e3c19d8175b3 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Thu, 13 Mar 2025 17:50:02 -0400 Subject: [PATCH 05/14] Fixes after merge --- .../sql/where/condition/IsInCaseInsensitiveWhenPresent.java | 2 +- .../sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 abd6758e0..de17a8394 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 @@ -36,7 +36,7 @@ public static IsInCaseInsensitiveWhenPresent empty() { return t; } - protected IsInCaseInsensitiveWhenPresent(Collection values) { + protected IsInCaseInsensitiveWhenPresent(Collection values) { super(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 c2784d819..bbff09bbe 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 @@ -36,7 +36,7 @@ public static IsNotInCaseInsensitiveWhenPresent empty() { return t; } - protected IsNotInCaseInsensitiveWhenPresent(Collection values) { + protected IsNotInCaseInsensitiveWhenPresent(Collection values) { super(values); } From 7b426ceb0a3795112a15867670d8007c002240ed Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 14 Mar 2025 09:40:32 -0400 Subject: [PATCH 06/14] Make filtering optional on no value conditions --- .../dynamic/sql/AbstractNoValueCondition.java | 31 ++++++++++--------- .../sql/where/condition/IsNotNull.java | 2 +- .../dynamic/sql/where/condition/IsNull.java | 2 +- .../examples/mysql/MemberOfCondition.java | 21 ------------- 4 files changed, 18 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java index f28073bf2..b0c7d9bad 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java @@ -34,22 +34,23 @@ protected > S filterSupport(BooleanSupplie public abstract String operator(); - /** - * 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 - * 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. - */ - public abstract AbstractNoValueCondition filter(BooleanSupplier booleanSupplier); - - - @Override + @Override public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn leftColumn) { return FragmentAndParameters.fromFragment(operator()); } + + 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 + * 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. + */ + AbstractNoValueCondition filter(BooleanSupplier booleanSupplier); + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java index fa13adbaf..1c1f3139d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java @@ -19,7 +19,7 @@ import org.mybatis.dynamic.sql.AbstractNoValueCondition; -public class IsNotNull extends AbstractNoValueCondition { +public class IsNotNull extends AbstractNoValueCondition implements AbstractNoValueCondition.Filterable { private static final IsNotNull EMPTY = new IsNotNull<>() { @Override public boolean isEmpty() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java index befbb9a13..a27b7dc2a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java @@ -19,7 +19,7 @@ import org.mybatis.dynamic.sql.AbstractNoValueCondition; -public class IsNull extends AbstractNoValueCondition { +public class IsNull extends AbstractNoValueCondition implements AbstractNoValueCondition.Filterable { private static final IsNull EMPTY = new IsNull<>() { @Override public boolean isEmpty() { diff --git a/src/test/java/examples/mysql/MemberOfCondition.java b/src/test/java/examples/mysql/MemberOfCondition.java index c805b2127..33b967556 100644 --- a/src/test/java/examples/mysql/MemberOfCondition.java +++ b/src/test/java/examples/mysql/MemberOfCondition.java @@ -16,26 +16,12 @@ package examples.mysql; import java.util.Objects; -import java.util.function.BooleanSupplier; import org.jspecify.annotations.NullMarked; import org.mybatis.dynamic.sql.AbstractNoValueCondition; @NullMarked public class MemberOfCondition extends AbstractNoValueCondition { - private static final MemberOfCondition EMPTY = new MemberOfCondition<>("") { - @Override - public boolean isEmpty() { - return true; - } - }; - - public static MemberOfCondition empty() { - @SuppressWarnings("unchecked") - MemberOfCondition t = (MemberOfCondition) EMPTY; - return t; - } - private final String jsonArray; protected MemberOfCondition(String jsonArray) { @@ -47,13 +33,6 @@ public String operator() { return "member of(" + jsonArray + ")"; } - @Override - public MemberOfCondition filter(BooleanSupplier booleanSupplier) { - @SuppressWarnings("unchecked") - MemberOfCondition self = (MemberOfCondition) this; - return filterSupport(booleanSupplier, MemberOfCondition::empty, self); - } - public static MemberOfCondition memberOf(String jsonArray) { return new MemberOfCondition<>(jsonArray); } From 30fc7efdfd868761fc653f4655002d9b2ccb9f82 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 14 Mar 2025 10:12:56 -0400 Subject: [PATCH 07/14] Make filtering optional on no value conditions --- .../mybatis/dynamic/sql/AbstractNoValueCondition.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java index b0c7d9bad..20e6251d6 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java @@ -39,6 +39,17 @@ public FragmentAndParameters renderCondition(RenderingContext renderingContext, return FragmentAndParameters.fromFragment(operator()); } + /** + * Conditions may implement Filterable to add optionality to rendering. + * + *

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. + * + *

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 From 50944221f6e15df6f95c57887da3c58011212444 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 14 Mar 2025 10:45:37 -0400 Subject: [PATCH 08/14] Make filter and map optional on single value conditions --- .../sql/AbstractSingleValueCondition.java | 71 +++++++++++++------ .../sql/where/condition/IsEqualTo.java | 3 +- .../sql/where/condition/IsGreaterThan.java | 3 +- .../condition/IsGreaterThanOrEqualTo.java | 3 +- .../sql/where/condition/IsLessThan.java | 4 +- .../where/condition/IsLessThanOrEqualTo.java | 3 +- .../dynamic/sql/where/condition/IsLike.java | 4 +- .../condition/IsLikeCaseInsensitive.java | 3 +- .../sql/where/condition/IsNotEqualTo.java | 3 +- .../sql/where/condition/IsNotLike.java | 3 +- .../condition/IsNotLikeCaseInsensitive.java | 14 +--- .../java/examples/mysql/IsLikeEscape.java | 3 +- .../kotlin/mybatis3/mariadb/KIsLikeEscape.kt | 3 +- 13 files changed, 77 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java index f032d64db..c16dbf08c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java @@ -54,27 +54,6 @@ protected > S mapSupport(Function filter(Predicate predicate); - - /** - * 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 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. - */ - public abstract AbstractSingleValueCondition map(Function mapper); - public abstract String operator(); @Override @@ -86,4 +65,54 @@ public FragmentAndParameters renderCondition(RenderingContext renderingContext, .withParameter(parameterInfo.parameterMapKey(), leftColumn.convertParameterType(value())) .build(); } + + /** + * Conditions may implement Filterable to add optionality to rendering. + * + *

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. + * + *

Implementations of Filterable may call + * {@link AbstractSingleValueCondition#filterSupport(Predicate, Supplier, AbstractSingleValueCondition)} as + * a common implementation of the filtering algorithm. + * + * @param the Java type related to the database column type + */ + public interface Filterable { + /** + * 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 filter(Predicate predicate); + } + + /** + * Conditions may implement Mappable to alter condition values or types during rendering. + * + *

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. + * + *

Implementations of Mappable may call + * {@link AbstractSingleValueCondition#mapSupport(Function, Function, Supplier)} as + * a common implementation of the mapping algorithm. + * + * @param the Java type related to the database column type + */ + public interface Mappable { + /** + * 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 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. + */ + AbstractSingleValueCondition map(Function mapper); + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java index 17fe9eef1..51e6e4d47 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java @@ -21,7 +21,8 @@ import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsEqualTo extends AbstractSingleValueCondition { +public class IsEqualTo extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { private static final IsEqualTo EMPTY = new IsEqualTo(-1) { @Override diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java index 2b3162e1a..93be70911 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java @@ -21,7 +21,8 @@ import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsGreaterThan extends AbstractSingleValueCondition { +public class IsGreaterThan extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { private static final IsGreaterThan EMPTY = new IsGreaterThan(-1) { @Override public Object value() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java index 7439ae4a8..8373bf352 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java @@ -21,7 +21,8 @@ import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsGreaterThanOrEqualTo extends AbstractSingleValueCondition { +public class IsGreaterThanOrEqualTo extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { private static final IsGreaterThanOrEqualTo EMPTY = new IsGreaterThanOrEqualTo(-1) { @Override public Object value() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java index 01614d6dc..3ed383fbd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java @@ -21,7 +21,9 @@ import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsLessThan extends AbstractSingleValueCondition { +public class IsLessThan extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsLessThan EMPTY = new IsLessThan(-1) { @Override public Object value() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java index 20c3ed460..1b92e0c40 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java @@ -21,7 +21,8 @@ import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsLessThanOrEqualTo extends AbstractSingleValueCondition { +public class IsLessThanOrEqualTo extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { private static final IsLessThanOrEqualTo EMPTY = new IsLessThanOrEqualTo(-1) { @Override public Object value() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java index 5c7ba967a..e738bda4c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java @@ -21,7 +21,9 @@ import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsLike extends AbstractSingleValueCondition { +public class IsLike extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsLike EMPTY = new IsLike(-1) { @Override public Object value() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java index c7d8deefe..b1675a44d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java @@ -23,7 +23,8 @@ import org.mybatis.dynamic.sql.util.StringUtilities; public class IsLikeCaseInsensitive extends AbstractSingleValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition, AbstractSingleValueCondition.Filterable, + AbstractSingleValueCondition.Mappable { private static final IsLikeCaseInsensitive EMPTY = new IsLikeCaseInsensitive<>("") { //$NON-NLS-1$ @Override public String value() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java index e52ab0385..39070c2e8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java @@ -21,7 +21,8 @@ import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsNotEqualTo extends AbstractSingleValueCondition { +public class IsNotEqualTo extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { private static final IsNotEqualTo EMPTY = new IsNotEqualTo(-1) { @Override public Object value() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java index a2cf60310..a62dc3e9e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java @@ -21,7 +21,8 @@ import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsNotLike extends AbstractSingleValueCondition { +public class IsNotLike extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { private static final IsNotLike EMPTY = new IsNotLike(-1) { @Override public Object value() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java index 8aded422d..5b64ef5f5 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java @@ -23,7 +23,8 @@ import org.mybatis.dynamic.sql.util.StringUtilities; public class IsNotLikeCaseInsensitive extends AbstractSingleValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition, AbstractSingleValueCondition.Filterable, + AbstractSingleValueCondition.Mappable { private static final IsNotLikeCaseInsensitive EMPTY = new IsNotLikeCaseInsensitive<>("") { //$NON-NLS-1$ @Override public String value() { @@ -56,16 +57,7 @@ public IsNotLikeCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsNotLikeCaseInsensitive::empty, this); } - /** - * 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 - * - * @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. - */ + @Override public IsNotLikeCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsNotLikeCaseInsensitive::new, IsNotLikeCaseInsensitive::empty); } diff --git a/src/test/java/examples/mysql/IsLikeEscape.java b/src/test/java/examples/mysql/IsLikeEscape.java index 21b43fa13..6b8799abb 100644 --- a/src/test/java/examples/mysql/IsLikeEscape.java +++ b/src/test/java/examples/mysql/IsLikeEscape.java @@ -27,7 +27,8 @@ import org.mybatis.dynamic.sql.util.FragmentAndParameters; @NullMarked -public class IsLikeEscape extends AbstractSingleValueCondition { +public class IsLikeEscape extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { private static final IsLikeEscape EMPTY = new IsLikeEscape(-1, null) { @Override public Object value() { diff --git a/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt b/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt index 06dca6609..b47f09110 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt @@ -10,7 +10,8 @@ import org.mybatis.dynamic.sql.util.FragmentAndParameters sealed class KIsLikeEscape( value: T, private val escapeCharacter: Char? = null -) : AbstractSingleValueCondition(value) { +) : AbstractSingleValueCondition(value), AbstractSingleValueCondition.Filterable, + AbstractSingleValueCondition.Mappable { override fun operator(): String = "like" From 43fdf0651330c71c810e49a730e47efbfc28d9bb Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 14 Mar 2025 10:53:52 -0400 Subject: [PATCH 09/14] Make filter and map optional on list value conditions --- .../sql/AbstractListValueCondition.java | 70 +++++++++++++------ .../dynamic/sql/where/condition/IsIn.java | 6 +- .../where/condition/IsInCaseInsensitive.java | 3 +- .../IsInCaseInsensitiveWhenPresent.java | 3 +- .../sql/where/condition/IsInWhenPresent.java | 15 ++-- .../dynamic/sql/where/condition/IsNotIn.java | 6 +- .../condition/IsNotInCaseInsensitive.java | 3 +- .../IsNotInCaseInsensitiveWhenPresent.java | 3 +- .../where/condition/IsNotInWhenPresent.java | 15 ++-- 9 files changed, 72 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java index 066012e28..41c6a56e2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java @@ -73,26 +73,6 @@ protected > S mapSupport(Function filter(Predicate predicate); - - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @param type of the new condition - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public abstract AbstractListValueCondition map(Function mapper); - public abstract String operator(); @Override @@ -110,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. + * + *

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. + * + *

Implementations of Filterable may call + * {@link AbstractListValueCondition#filterSupport(Predicate, Function, AbstractListValueCondition, Supplier)} as + * a common implementation of the filtering algorithm. + * + * @param the Java type related to the database column type + */ + public interface Filterable { + /** + * 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 filter(Predicate predicate); + } + + /** + * Conditions may implement Mappable to alter condition values or types during rendering. + * + *

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. + * + *

Implementations of Mappable may call + * {@link AbstractListValueCondition#mapSupport(Function, Function, Supplier)} as + * a common implementation of the mapping algorithm. + * + * @param the Java type related to the database column type + */ + public interface Mappable { + /** + * 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 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. + */ + AbstractListValueCondition map(Function mapper); + } } 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 67b6fff08..8beeacc8d 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 @@ -25,7 +25,8 @@ import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.Validator; -public class IsIn extends AbstractListValueCondition { +public class IsIn extends AbstractListValueCondition + implements AbstractListValueCondition.Filterable, AbstractListValueCondition.Mappable{ private static final IsIn EMPTY = new IsIn<>(Collections.emptyList()); public static IsIn empty() { @@ -56,8 +57,7 @@ public IsIn filter(Predicate predicate) { @Override public IsIn map(Function mapper) { - Function, IsIn> constructor = IsIn::new; - return mapSupport(mapper, constructor, IsIn::empty); + return mapSupport(mapper, IsIn::new, IsIn::empty); } @SafeVarargs 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 6d0219dcd..6ca85d554 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 @@ -27,7 +27,8 @@ import org.mybatis.dynamic.sql.util.Validator; public class IsInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, + AbstractListValueCondition.Mappable { private static final IsInCaseInsensitive EMPTY = new IsInCaseInsensitive<>(Collections.emptyList()); public static IsInCaseInsensitive empty() { 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 de17a8394..6afdb486d 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 @@ -26,7 +26,8 @@ import org.mybatis.dynamic.sql.AbstractListValueCondition; public class IsInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, + AbstractListValueCondition.Mappable { private static final IsInCaseInsensitiveWhenPresent EMPTY = new IsInCaseInsensitiveWhenPresent<>(Collections.emptyList()); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWhenPresent.java index ea1bfe509..5c431fea3 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWhenPresent.java @@ -25,7 +25,8 @@ import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractListValueCondition; -public class IsInWhenPresent extends AbstractListValueCondition { +public class IsInWhenPresent extends AbstractListValueCondition + implements AbstractListValueCondition.Filterable, AbstractListValueCondition.Mappable{ private static final IsInWhenPresent EMPTY = new IsInWhenPresent<>(Collections.emptyList()); public static IsInWhenPresent empty() { @@ -48,17 +49,9 @@ public IsInWhenPresent filter(Predicate predicate) { return filterSupport(predicate, IsInWhenPresent::new, this, IsInWhenPresent::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @param type of the new condition - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ + @Override public IsInWhenPresent map(Function mapper) { - Function, IsInWhenPresent> constructor = IsInWhenPresent::new; - return mapSupport(mapper, constructor, IsInWhenPresent::empty); + return mapSupport(mapper, IsInWhenPresent::new, IsInWhenPresent::empty); } @SafeVarargs 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 f22c504c8..33f5d14c7 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 @@ -25,7 +25,8 @@ import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.Validator; -public class IsNotIn extends AbstractListValueCondition { +public class IsNotIn extends AbstractListValueCondition + implements AbstractListValueCondition.Filterable, AbstractListValueCondition.Mappable{ private static final IsNotIn EMPTY = new IsNotIn<>(Collections.emptyList()); public static IsNotIn empty() { @@ -56,8 +57,7 @@ public IsNotIn filter(Predicate predicate) { @Override public IsNotIn map(Function mapper) { - Function, IsNotIn> constructor = IsNotIn::new; - return mapSupport(mapper, constructor, IsNotIn::empty); + return mapSupport(mapper, IsNotIn::new, IsNotIn::empty); } @SafeVarargs 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 3513d022c..7ade40938 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 @@ -27,7 +27,8 @@ import org.mybatis.dynamic.sql.util.Validator; public class IsNotInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, + AbstractListValueCondition.Mappable { private static final IsNotInCaseInsensitive EMPTY = new IsNotInCaseInsensitive<>(Collections.emptyList()); public static IsNotInCaseInsensitive empty() { 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 bbff09bbe..9d57e7623 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 @@ -26,7 +26,8 @@ import org.mybatis.dynamic.sql.AbstractListValueCondition; public class IsNotInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveRenderableCondition { + implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, + AbstractListValueCondition.Mappable { private static final IsNotInCaseInsensitiveWhenPresent EMPTY = new IsNotInCaseInsensitiveWhenPresent<>(Collections.emptyList()); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWhenPresent.java index 1c4b570a9..ddc8e5470 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWhenPresent.java @@ -24,7 +24,8 @@ import org.mybatis.dynamic.sql.AbstractListValueCondition; -public class IsNotInWhenPresent extends AbstractListValueCondition { +public class IsNotInWhenPresent extends AbstractListValueCondition + implements AbstractListValueCondition.Filterable, AbstractListValueCondition.Mappable{ private static final IsNotInWhenPresent EMPTY = new IsNotInWhenPresent<>(Collections.emptyList()); public static IsNotInWhenPresent empty() { @@ -47,17 +48,9 @@ public IsNotInWhenPresent filter(Predicate predicate) { return filterSupport(predicate, IsNotInWhenPresent::new, this, IsNotInWhenPresent::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @param type of the new condition - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ + @Override public IsNotInWhenPresent map(Function mapper) { - Function, IsNotInWhenPresent> constructor = IsNotInWhenPresent::new; - return mapSupport(mapper, constructor, IsNotInWhenPresent::empty); + return mapSupport(mapper, IsNotInWhenPresent::new, IsNotInWhenPresent::empty); } @SafeVarargs From 1a2b9189270dd1b2c8f0bb02e1a8bbd6820da5b1 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 14 Mar 2025 11:08:08 -0400 Subject: [PATCH 10/14] Make filter and map optional on two value conditions --- .../sql/AbstractTwoValueCondition.java | 120 +++++++++++------- .../sql/where/condition/IsBetween.java | 3 +- .../sql/where/condition/IsNotBetween.java | 3 +- 3 files changed, 79 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java index 15a5b6ffe..6cceff16e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java @@ -67,51 +67,6 @@ protected > S mapSupport(Function filter(BiPredicate 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 filter(Predicate predicate); - - /** - * 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 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. - */ - public abstract AbstractTwoValueCondition map(Function mapper1, - Function 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 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. - */ - public abstract AbstractTwoValueCondition map(Function mapper); - public abstract String operator1(); public abstract String operator2(); @@ -131,4 +86,79 @@ public FragmentAndParameters renderCondition(RenderingContext renderingContext, .withParameter(parameterInfo2.parameterMapKey(), leftColumn.convertParameterType(value2())) .build(); } + + /** + * Conditions may implement Filterable to add optionality to rendering. + * + *

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. + * + *

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 the Java type related to the database column type + */ + public interface Filterable { + /** + * 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 filter(BiPredicate 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 filter(Predicate predicate); + } + + /** + * Conditions may implement Mappable to alter condition values or types during rendering. + * + *

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. + * + *

Implementations of Mappable may call + * {@link AbstractTwoValueCondition#mapSupport(Function, Function, BiFunction, Supplier)} as + * a common implementation of the mapping algorithm. + * + * @param the Java type related to the database column type + */ + public interface Mappable { + /** + * 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 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. + */ + AbstractTwoValueCondition map(Function mapper1, + Function 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 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. + */ + AbstractTwoValueCondition map(Function mapper); + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java index 0d554d0fc..0e6700fb0 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java @@ -23,7 +23,8 @@ import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractTwoValueCondition; -public class IsBetween extends AbstractTwoValueCondition { +public class IsBetween extends AbstractTwoValueCondition + implements AbstractTwoValueCondition.Filterable, AbstractTwoValueCondition.Mappable { private static final IsBetween EMPTY = new IsBetween(-1, -1) { @Override public Object value1() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java index f7eb98f52..6a515d4ac 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java @@ -23,7 +23,8 @@ import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractTwoValueCondition; -public class IsNotBetween extends AbstractTwoValueCondition { +public class IsNotBetween extends AbstractTwoValueCondition + implements AbstractTwoValueCondition.Filterable, AbstractTwoValueCondition.Mappable { private static final IsNotBetween EMPTY = new IsNotBetween(-1, -1) { @Override public Object value1() { From 4594a5f4995f7d76d20eb5366fcca429ce4c412b Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 14 Mar 2025 11:19:14 -0400 Subject: [PATCH 11/14] Update docs --- src/site/markdown/docs/extending.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/site/markdown/docs/extending.md b/src/site/markdown/docs/extending.md index eabee1e03..a1fee9adb 100644 --- a/src/site/markdown/docs/extending.md +++ b/src/site/markdown/docs/extending.md @@ -304,7 +304,8 @@ Here's an example of implementing a LIKE condition that supports ESCAPE: ```java @NullMarked -public class IsLikeEscape extends AbstractSingleValueCondition { +public class IsLikeEscape extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { private static final IsLikeEscape EMPTY = new IsLikeEscape(-1, null) { @Override public Object value() { @@ -354,6 +355,7 @@ public class IsLikeEscape extends AbstractSingleValueCondition { return filterSupport(predicate, IsLikeEscape::empty, this); } + @Override public IsLikeEscape map(Function mapper) { return mapSupport(mapper, v -> new IsLikeEscape<>(v, escapeCharacter), IsLikeEscape::empty); } @@ -374,4 +376,5 @@ Important notes: 2. The class constructor accepts an escape character that will be rendered into an ESCAPE phrase 3. The class overrides `renderCondition` and changes the library generated `FragmentAndParameters` to add the ESCAPE phrase. **This is the key to what's needed to implement a custom condition.** -4. The class provides `map` and `filter` functions as is expected for any condition in the library +4. The class implements `Filterable` and `Mappable` to provide `filter` and `map` functions as is expected for most + conditions in the library From 0acbda7617d41b0a5608beccda8074499fbe436b Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 14 Mar 2025 12:32:53 -0400 Subject: [PATCH 12/14] Update docs --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b333e3ab..cec9f683f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From 1df401a0c1e7493ea6cb0f37423bee2c4a8668e4 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 14 Mar 2025 12:54:59 -0400 Subject: [PATCH 13/14] Polishing --- .../sql/where/condition/SupplierTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/mybatis/dynamic/sql/where/condition/SupplierTest.java b/src/test/java/org/mybatis/dynamic/sql/where/condition/SupplierTest.java index e1362d48f..b85a7f412 100644 --- a/src/test/java/org/mybatis/dynamic/sql/where/condition/SupplierTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/where/condition/SupplierTest.java @@ -260,28 +260,28 @@ void testIsLikeNull() { @Test void testIsLikeCaseInsensitive() { - IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitive(() -> "%f%"); + IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitive(() -> "%f%"); assertThat(cond.value()).isEqualTo("%F%"); assertThat(cond.isEmpty()).isFalse(); } @Test void testIsLikeCaseInsensitiveNull() { - IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitive(() -> null); + IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitive(() -> null); assertThat(cond.value()).isNull(); assertThat(cond.isEmpty()).isFalse(); } @Test void testIsLikeCaseInsensitiveWhenPresent() { - IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitiveWhenPresent(() -> "%f%"); + IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitiveWhenPresent(() -> "%f%"); assertThat(cond.value()).isEqualTo("%F%"); assertThat(cond.isEmpty()).isFalse(); } @Test void testIsLikeCaseInsensitiveWhenPresentNull() { - IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitiveWhenPresent(() -> null); + IsLikeCaseInsensitive cond = SqlBuilder.isLikeCaseInsensitiveWhenPresent(() -> null); assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(cond::value); assertThat(cond.isEmpty()).isTrue(); } @@ -330,28 +330,28 @@ void testIsNotLikeWhenPresentNull() { @Test void testIsNotLikeCaseInsensitive() { - IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitive(() -> "%f%"); + IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitive(() -> "%f%"); assertThat(cond.value()).isEqualTo("%F%"); assertThat(cond.isEmpty()).isFalse(); } @Test void testIsNotLikeCaseInsensitiveNull() { - IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitive(() -> null); + IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitive(() -> null); assertThat(cond.value()).isNull(); assertThat(cond.isEmpty()).isFalse(); } @Test void testIsNotLikeCaseInsensitiveWhenPresent() { - IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitiveWhenPresent(() -> "%f%"); + IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitiveWhenPresent(() -> "%f%"); assertThat(cond.value()).isEqualTo("%F%"); assertThat(cond.isEmpty()).isFalse(); } @Test void testIsNotLikeCaseInsensitiveWhenPresentNull() { - IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitiveWhenPresent(() -> null); + IsNotLikeCaseInsensitive cond = SqlBuilder.isNotLikeCaseInsensitiveWhenPresent(() -> null); assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(cond::value); assertThat(cond.isEmpty()).isTrue(); } From 7cb17bd932f150a6765b0751244917b4c22599c6 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 14 Mar 2025 16:17:05 -0400 Subject: [PATCH 14/14] Coverage --- .../dynamic/sql/util/StringUtilities.java | 6 +++ .../where/condition/IsInCaseInsensitive.java | 13 +++++++ .../IsInCaseInsensitiveWhenPresent.java | 14 +++++++ .../condition/IsLikeCaseInsensitive.java | 13 +++++++ .../condition/IsNotInCaseInsensitive.java | 13 +++++++ .../IsNotInCaseInsensitiveWhenPresent.java | 14 +++++++ .../condition/IsNotLikeCaseInsensitive.java | 13 +++++++ .../sql/where/condition/FilterAndMapTest.java | 37 +++++++++++++++++++ 8 files changed, 123 insertions(+) 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 2b57c748e..25a7e390c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java @@ -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 mapToUpperCase(Function f) { + return f.andThen(String::toUpperCase); + } } 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 6ca85d554..dc3b0f8f1 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 @@ -57,6 +57,19 @@ public IsInCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsInCaseInsensitive::new, this, IsInCaseInsensitive::empty); } + /** + * 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). + * + *

This function DOES NOT automatically transform values to uppercase, so it potentially creates a + * case-sensitive query. For String conditions you can use {@link StringUtilities#mapToUpperCase(Function)} + * to add an uppercase transform after your mapping function. + * + * @param mapper a mapping function to apply to the value, if renderable + * @param 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. + */ @Override public IsInCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsInCaseInsensitive::new, IsInCaseInsensitive::empty); 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 6afdb486d..283e62fc7 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 @@ -24,6 +24,7 @@ import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractListValueCondition; +import org.mybatis.dynamic.sql.util.StringUtilities; public class IsInCaseInsensitiveWhenPresent extends AbstractListValueCondition implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, @@ -52,6 +53,19 @@ public IsInCaseInsensitiveWhenPresent filter(Predicate predicate) IsInCaseInsensitiveWhenPresent::empty); } + /** + * 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). + * + *

This function DOES NOT automatically transform values to uppercase, so it potentially creates a + * case-sensitive query. For String conditions you can use {@link StringUtilities#mapToUpperCase(Function)} + * to add an uppercase transform after your mapping function. + * + * @param mapper a mapping function to apply to the value, if renderable + * @param 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. + */ @Override public IsInCaseInsensitiveWhenPresent map(Function mapper) { return mapSupport(mapper, IsInCaseInsensitiveWhenPresent::new, IsInCaseInsensitiveWhenPresent::empty); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java index b1675a44d..6b569b32d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java @@ -57,6 +57,19 @@ public IsLikeCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsLikeCaseInsensitive::empty, this); } + /** + * 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). + * + *

This function DOES NOT automatically transform values to uppercase, so it potentially creates a + * case-sensitive query. For String conditions you can use {@link StringUtilities#mapToUpperCase(Function)} + * to add an uppercase transform after your mapping function. + * + * @param mapper a mapping function to apply to the value, if renderable + * @param 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. + */ @Override public IsLikeCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsLikeCaseInsensitive::new, IsLikeCaseInsensitive::empty); 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 7ade40938..8731d5b64 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 @@ -57,6 +57,19 @@ public IsNotInCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsNotInCaseInsensitive::new, this, IsNotInCaseInsensitive::empty); } + /** + * 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). + * + *

This function DOES NOT automatically transform values to uppercase, so it potentially creates a + * case-sensitive query. For String conditions you can use {@link StringUtilities#mapToUpperCase(Function)} + * to add an uppercase transform after your mapping function. + * + * @param mapper a mapping function to apply to the value, if renderable + * @param 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. + */ @Override public IsNotInCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsNotInCaseInsensitive::new, IsNotInCaseInsensitive::empty); 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 9d57e7623..438e1c5ed 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 @@ -24,6 +24,7 @@ import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractListValueCondition; +import org.mybatis.dynamic.sql.util.StringUtilities; public class IsNotInCaseInsensitiveWhenPresent extends AbstractListValueCondition implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, @@ -52,6 +53,19 @@ public IsNotInCaseInsensitiveWhenPresent filter(Predicate predicat this, IsNotInCaseInsensitiveWhenPresent::empty); } + /** + * 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). + * + *

This function DOES NOT automatically transform values to uppercase, so it potentially creates a + * case-sensitive query. For String conditions you can use {@link StringUtilities#mapToUpperCase(Function)} + * to add an uppercase transform after your mapping function. + * + * @param mapper a mapping function to apply to the value, if renderable + * @param 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. + */ @Override public IsNotInCaseInsensitiveWhenPresent map(Function mapper) { return mapSupport(mapper, IsNotInCaseInsensitiveWhenPresent::new, IsNotInCaseInsensitiveWhenPresent::empty); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java index 5b64ef5f5..8eb65d772 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java @@ -57,6 +57,19 @@ public IsNotLikeCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsNotLikeCaseInsensitive::empty, this); } + /** + * 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). + * + *

This function DOES NOT automatically transform values to uppercase, so it potentially creates a + * case-sensitive query. For String conditions you can use {@link StringUtilities#mapToUpperCase(Function)} + * to add an uppercase transform after your mapping function. + * + * @param mapper a mapping function to apply to the value, if renderable + * @param 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. + */ @Override public IsNotLikeCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsNotLikeCaseInsensitive::new, IsNotLikeCaseInsensitive::empty); diff --git a/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java b/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java index 8c8f35eb5..7d4e4505e 100644 --- a/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/where/condition/FilterAndMapTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import org.mybatis.dynamic.sql.SqlBuilder; +import org.mybatis.dynamic.sql.util.StringUtilities; class FilterAndMapTest { @Test @@ -533,4 +534,40 @@ void testNotBetweenMapWithSingleMapper() { assertThat(cond.value2()).isEqualTo(4); } + @Test + void testMappingAnEmptyListCondition() { + var cond = SqlBuilder.isNotIn("Fred", "Wilma"); + var filtered = cond.filter(s -> false); + var mapped = filtered.map(s -> s); + assertThat(mapped.isEmpty()).isTrue(); + assertThat(filtered).isSameAs(mapped); + } + + @Test + void testIsInCaseInsensitiveWhenPresentMap() { + var cond = SqlBuilder.isInCaseInsensitiveWhenPresent("Fred", "Wilma"); + var mapped = cond.map(s -> s + " Flintstone"); + assertThat(mapped.values().toList()).containsExactly("FRED Flintstone", "WILMA Flintstone"); + } + + @Test + void testIsInCaseInsensitiveWhenPresentMapCaseInsensitive() { + var cond = SqlBuilder.isInCaseInsensitiveWhenPresent("Fred", "Wilma"); + var mapped = cond.map(StringUtilities.mapToUpperCase(s -> s + " Flintstone")); + assertThat(mapped.values().toList()).containsExactly("FRED FLINTSTONE", "WILMA FLINTSTONE"); + } + + @Test + void testIsNotInCaseInsensitiveWhenPresentMap() { + var cond = SqlBuilder.isNotInCaseInsensitiveWhenPresent("Fred", "Wilma"); + var mapped = cond.map(s -> s + " Flintstone"); + assertThat(mapped.values().toList()).containsExactly("FRED Flintstone", "WILMA Flintstone"); + } + + @Test + void testIsNotInCaseInsensitiveWhenPresentMapCaseInsensitive() { + var cond = SqlBuilder.isNotInCaseInsensitiveWhenPresent("Fred", "Wilma"); + var mapped = cond.map(StringUtilities.mapToUpperCase(s -> s + " Flintstone")); + assertThat(mapped.values().toList()).containsExactly("FRED FLINTSTONE", "WILMA FLINTSTONE"); + } }