Skip to content

Improve case insensitive conditions #923

New issue

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

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

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
*/
package org.mybatis.dynamic.sql.util;

import java.util.function.Function;

import org.jspecify.annotations.Nullable;

public interface StringUtilities {

static String spaceAfter(String in) {
Expand All @@ -29,10 +25,6 @@ static String spaceBefore(String in) {
return " " + in; //$NON-NLS-1$
}

static @Nullable String safelyUpperCase(@Nullable String s) {
return s == null ? null : s.toUpperCase();
}

static String toCamelCase(String inputString) {
StringBuilder sb = new StringBuilder();

Expand Down Expand Up @@ -62,7 +54,13 @@ static String formatConstantForSQL(String in) {
return "'" + escaped + "'"; //$NON-NLS-1$ //$NON-NLS-2$
}

static Function<String, String> mapToUpperCase(Function<String, String> f) {
return f.andThen(String::toUpperCase);
static <T> T upperCaseIfPossible(T value) {
if (value instanceof String) {
@SuppressWarnings("unchecked")
T t = (T) ((String) value).toUpperCase();
return t;
}

return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static <T> IsInCaseInsensitive<T> empty() {
}

protected IsInCaseInsensitive(Collection<T> values) {
super(values);
super(values.stream().map(StringUtilities::upperCaseIfPossible).toList());
}

@Override
Expand All @@ -57,19 +57,6 @@ public IsInCaseInsensitive<T> filter(Predicate<? super T> 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).
*
* <p>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 <R> type of the new condition
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
@Override
public <R> IsInCaseInsensitive<R> map(Function<? super T, ? extends R> mapper) {
return mapSupport(mapper, IsInCaseInsensitive::new, IsInCaseInsensitive::empty);
Expand All @@ -80,7 +67,6 @@ public static IsInCaseInsensitive<String> of(String... values) {
}

public static IsInCaseInsensitive<String> of(Collection<String> values) {
// Keep the null safe upper case utility for backwards compatibility in case someone passes in a null
return new IsInCaseInsensitive<>(values.stream().map(StringUtilities::safelyUpperCase).toList());
return new IsInCaseInsensitive<>(values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static <T> IsInCaseInsensitiveWhenPresent<T> empty() {
}

protected IsInCaseInsensitiveWhenPresent(Collection<T> values) {
super(values);
super(values.stream().filter(Objects::nonNull).map(StringUtilities::upperCaseIfPossible).toList());
}

@Override
Expand All @@ -53,19 +53,6 @@ public IsInCaseInsensitiveWhenPresent<T> filter(Predicate<? super T> 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).
*
* <p>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 <R> type of the new condition
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
@Override
public <R> IsInCaseInsensitiveWhenPresent<R> map(Function<? super T, ? extends R> mapper) {
return mapSupport(mapper, IsInCaseInsensitiveWhenPresent::new, IsInCaseInsensitiveWhenPresent::empty);
Expand All @@ -76,7 +63,6 @@ public static IsInCaseInsensitiveWhenPresent<String> of(@Nullable String... valu
}

public static IsInCaseInsensitiveWhenPresent<String> of(Collection<@Nullable String> values) {
return new IsInCaseInsensitiveWhenPresent<>(
values.stream().filter(Objects::nonNull).map(String::toUpperCase).toList());
return new IsInCaseInsensitiveWhenPresent<>(values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static <T> IsLikeCaseInsensitive<T> empty() {
}

protected IsLikeCaseInsensitive(T value) {
super(value);
super(StringUtilities.upperCaseIfPossible(value));
}

@Override
Expand All @@ -57,26 +57,12 @@ public IsLikeCaseInsensitive<T> filter(Predicate<? super T> 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).
*
* <p>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 <R> type of the new condition
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
@Override
public <R> IsLikeCaseInsensitive<R> map(Function<? super T, ? extends R> mapper) {
return mapSupport(mapper, IsLikeCaseInsensitive::new, IsLikeCaseInsensitive::empty);
}

public static IsLikeCaseInsensitive<String> of(String value) {
// Keep the null safe upper case utility for backwards compatibility in case someone passes in a null
return new IsLikeCaseInsensitive<>(StringUtilities.safelyUpperCase(value));
return new IsLikeCaseInsensitive<>(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static <T> IsNotInCaseInsensitive<T> empty() {
}

protected IsNotInCaseInsensitive(Collection<T> values) {
super(values);
super(values.stream().map(StringUtilities::upperCaseIfPossible).toList());
}

@Override
Expand All @@ -57,19 +57,6 @@ public IsNotInCaseInsensitive<T> filter(Predicate<? super T> 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).
*
* <p>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 <R> type of the new condition
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
@Override
public <R> IsNotInCaseInsensitive<R> map(Function<? super T, ? extends R> mapper) {
return mapSupport(mapper, IsNotInCaseInsensitive::new, IsNotInCaseInsensitive::empty);
Expand All @@ -80,7 +67,6 @@ public static IsNotInCaseInsensitive<String> of(String... values) {
}

public static IsNotInCaseInsensitive<String> of(Collection<String> values) {
// Keep the null safe upper case utility for backwards compatibility in case someone passes in a null
return new IsNotInCaseInsensitive<>(values.stream().map(StringUtilities::safelyUpperCase).toList());
return new IsNotInCaseInsensitive<>(values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static <T> IsNotInCaseInsensitiveWhenPresent<T> empty() {
}

protected IsNotInCaseInsensitiveWhenPresent(Collection<T> values) {
super(values);
super(values.stream().filter(Objects::nonNull).map(StringUtilities::upperCaseIfPossible).toList());
}

@Override
Expand All @@ -53,19 +53,6 @@ public IsNotInCaseInsensitiveWhenPresent<T> filter(Predicate<? super T> 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).
*
* <p>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 <R> type of the new condition
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
@Override
public <R> IsNotInCaseInsensitiveWhenPresent<R> map(Function<? super T, ? extends R> mapper) {
return mapSupport(mapper, IsNotInCaseInsensitiveWhenPresent::new, IsNotInCaseInsensitiveWhenPresent::empty);
Expand All @@ -76,7 +63,6 @@ public static IsNotInCaseInsensitiveWhenPresent<String> of(@Nullable String... v
}

public static IsNotInCaseInsensitiveWhenPresent<String> of(Collection<@Nullable String> values) {
return new IsNotInCaseInsensitiveWhenPresent<>(
values.stream().filter(Objects::nonNull).map(String::toUpperCase).toList());
return new IsNotInCaseInsensitiveWhenPresent<>(values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static <T> IsNotLikeCaseInsensitive<T> empty() {
}

protected IsNotLikeCaseInsensitive(T value) {
super(value);
super(StringUtilities.upperCaseIfPossible(value));
}

@Override
Expand All @@ -57,26 +57,12 @@ public IsNotLikeCaseInsensitive<T> filter(Predicate<? super T> 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).
*
* <p>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 <R> type of the new condition
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
@Override
public <R> IsNotLikeCaseInsensitive<R> map(Function<? super T, ? extends R> mapper) {
return mapSupport(mapper, IsNotLikeCaseInsensitive::new, IsNotLikeCaseInsensitive::empty);
}

public static IsNotLikeCaseInsensitive<String> of(String value) {
// Keep the null safe upper case utility for backwards compatibility in case someone passes in a null
return new IsNotLikeCaseInsensitive<>(StringUtilities.safelyUpperCase(value));
return new IsNotLikeCaseInsensitive<>(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,16 @@ void testNumeric() {
String input = "USER%NAME%3";
assertThat(StringUtilities.toCamelCase(input)).isEqualTo("userName3");
}

@Test
void testUpperCaseInteger() {
Integer i = StringUtilities.upperCaseIfPossible(3);
assertThat(i).isEqualTo(3);
}

@Test
void testUpperCaseString() {
String i = StringUtilities.upperCaseIfPossible("fred");
assertThat(i).isEqualTo("FRED");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

import org.junit.jupiter.api.Test;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.mybatis.dynamic.sql.util.StringUtilities;

class FilterAndMapTest {
@Test
Expand Down Expand Up @@ -543,31 +542,17 @@ void testMappingAnEmptyListCondition() {
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");
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"));
var mapped = cond.map(s -> s + " Flintstone");
assertThat(mapped.values().toList()).containsExactly("FRED FLINTSTONE", "WILMA FLINTSTONE");
}
}