Skip to content

Commit

Permalink
Use COALESCE for LIKE with wildcards.
Browse files Browse the repository at this point in the history
To handle potential NULL values when LIKE is combined with wildcards, insert a COALESCE function.

See #3041
  • Loading branch information
gregturn committed Jun 29, 2023
1 parent 684ed61 commit 3f83904
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class StringQuery implements DeclaredQuery {

Metadata queryMeta = new Metadata();
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
this.bindings, queryMeta);
this.bindings, queryMeta, isNative);

this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters;

Expand Down Expand Up @@ -212,7 +212,7 @@ enum ParameterBindingParser {
* the cleaned up query.
*/
private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query,
List<ParameterBinding> bindings, Metadata queryMeta) {
List<ParameterBinding> bindings, Metadata queryMeta, boolean isNative) {

int greatestParameterIndex = tryFindGreatestParameterIndexIn(query);
boolean parametersShouldBeAccessedByIndex = greatestParameterIndex != -1;
Expand Down Expand Up @@ -299,7 +299,7 @@ private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(St
}

if (replacement != null) {
resultingQuery = replaceFirst(resultingQuery, matcher.group(2), replacement);
resultingQuery = replaceFirst(resultingQuery, matcher.group(2), replacement, isNative);
}

}
Expand Down Expand Up @@ -327,14 +327,14 @@ private static SpelExtractor createSpelExtractor(String queryWithSpel, boolean p
return SpelQueryContext.of(indexToParameterName, parameterNameToReplacement).parse(queryWithSpel);
}

private static String replaceFirst(String text, String substring, String replacement) {
private static String replaceFirst(String text, String substring, String replacement, boolean isNative) {

int index = text.indexOf(substring);
if (index < 0) {
return text;
}

return text.substring(0, index) + potentiallyWrapWithWildcards(replacement, substring)
return text.substring(0, index) + potentiallyWrapWithWildcards(replacement, substring, isNative)
+ text.substring(index + substring.length());
}

Expand All @@ -348,7 +348,7 @@ private static String replaceFirst(String text, String substring, String replace
* @return the replacement string properly wrapped in a {@literal CONCAT} function with wildcards applied.
* @since 3.1
*/
private static String potentiallyWrapWithWildcards(String replacement, String substring) {
private static String potentiallyWrapWithWildcards(String replacement, String substring, boolean isNative) {

boolean wildcards = substring.startsWith("%") || substring.endsWith("%");

Expand All @@ -362,7 +362,11 @@ private static String potentiallyWrapWithWildcards(String replacement, String su
concatWrapper.append("'%',");
}

concatWrapper.append(replacement);
if (isNative) {
concatWrapper.append(coalesce(replacement, "''"));
} else {
concatWrapper.append(coalesce(castAsString(replacement), "''"));
}

if (substring.endsWith("%")) {
concatWrapper.append(",'%'");
Expand All @@ -373,6 +377,27 @@ private static String potentiallyWrapWithWildcards(String replacement, String su
return concatWrapper.toString();
}

/**
* Apply the {@literal COALESCE} function with a default value.
*
* @param expression
* @param defaultValue
* @return
*/
private static String coalesce(String expression, String defaultValue) {
return "COALESCE(" + expression + "," + defaultValue + ")";
}

/**
* {@literal CAST} the expression into a {@literal String}.
*
* @param expression
* @return
*/
private static String castAsString(String expression) {
return "CAST(" + expression + " as text)";
}

@Nullable
private static Integer getParameterIndex(@Nullable String parameterIndexString) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ void customQueryWithNullMatch() {

List<EmployeeWithName> Employees = repository.customQueryWithNullableParam(null);

assertThat(Employees).extracting(EmployeeWithName::getName).isEmpty();
assertThat(Employees).extracting(EmployeeWithName::getName).containsExactlyInAnyOrder("Frodo Baggins",
"Bilbo Baggins");
}

@Test // GH-2939
Expand Down Expand Up @@ -133,7 +134,8 @@ void customQueryWithNullMatchInNative() {

List<EmployeeWithName> Employees = repository.customQueryWithNullableParamInNative(null);

assertThat(Employees).extracting(EmployeeWithName::getName).isEmpty();
assertThat(Employees).extracting(EmployeeWithName::getName).containsExactlyInAnyOrder("Frodo Baggins",
"Bilbo Baggins");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ void detectsPositionalLikeBindings() {
true);

assertThat(query.hasParameterBindings()).isTrue();
assertThat(query.getQueryString())
.isEqualTo("select u from User u where u.firstname like CONCAT('%',?1,'%') or u.lastname like CONCAT('%',?2)");
assertThat(query.getQueryString()).isEqualTo(
"select u from User u where u.firstname like CONCAT('%',COALESCE(?1,''),'%') or u.lastname like CONCAT('%',COALESCE(?2,''))");

List<ParameterBinding> bindings = query.getParameterBindings();
assertThat(bindings).hasSize(2);
Expand All @@ -87,7 +87,8 @@ void detectsNamedLikeBindings() {
StringQuery query = new StringQuery("select u from User u where u.firstname like %:firstname", true);

assertThat(query.hasParameterBindings()).isTrue();
assertThat(query.getQueryString()).isEqualTo("select u from User u where u.firstname like CONCAT('%',:firstname)");
assertThat(query.getQueryString())
.isEqualTo("select u from User u where u.firstname like CONCAT('%',COALESCE(:firstname,''))");

List<ParameterBinding> bindings = query.getParameterBindings();
assertThat(bindings).hasSize(1);
Expand Down Expand Up @@ -200,8 +201,8 @@ void removesLikeBindingsFromQueryIfQueryContainsSimpleBinding() {
assertNamedBinding(ParameterBinding.class, "word", bindings.get(1));

assertThat(query.getQueryString())
.isEqualTo("SELECT a FROM Article a WHERE a.overview LIKE CONCAT('%',:escapedWord,'%') ESCAPE '~'"
+ " OR a.content LIKE CONCAT('%',:escapedWord,'%') ESCAPE '~' OR a.title = :word ORDER BY a.articleId DESC");
.isEqualTo("SELECT a FROM Article a WHERE a.overview LIKE CONCAT('%',COALESCE(:escapedWord,''),'%') ESCAPE '~'"
+ " OR a.content LIKE CONCAT('%',COALESCE(:escapedWord,''),'%') ESCAPE '~' OR a.title = :word ORDER BY a.articleId DESC");
}

@Test // DATAJPA-483
Expand Down

0 comments on commit 3f83904

Please sign in to comment.