Use unique named parameter bindings for like parameters.
We now replace LIKE expressions according to their type with individual bindings if an existing binding cannot be used because of how the bound value is being transformed. WHERE foo like %:name or bar like :name becomes WHERE foo like :name (i.e. '%' + :name) or bar like :name_1 (i.e. :name) See #3041
This commit is contained in:
committed by
Greg L. Turnquist
parent
5f84e2aa63
commit
a1e6bbcd98
@@ -327,14 +327,15 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
}
|
||||
|
||||
/**
|
||||
* Because Hibernate's {@literal TypedParameterValue} is only used to wrap a {@literal null}, swap it out with an
|
||||
* empty string for query creation.
|
||||
* Because Hibernate's {@literal TypedParameterValue} is only used to wrap a {@literal null}, swap it out with
|
||||
* {@code null} for query creation.
|
||||
*
|
||||
* @param value
|
||||
* @return the original value or null.
|
||||
* @since 3.0
|
||||
*/
|
||||
public static Object unwrapTypedParameterValue(Object value) {
|
||||
@Nullable
|
||||
public static Object unwrapTypedParameterValue(@Nullable Object value) {
|
||||
|
||||
return typedParameterValueClass != null && typedParameterValueClass.isInstance(value) //
|
||||
? null //
|
||||
|
||||
@@ -46,10 +46,11 @@ class ParameterBinderFactory {
|
||||
|
||||
Assert.notNull(parameters, "JpaParameters must not be null");
|
||||
|
||||
QueryParameterSetterFactory likeFactory = QueryParameterSetterFactory.forLikeRewrite(parameters);
|
||||
QueryParameterSetterFactory setterFactory = QueryParameterSetterFactory.basic(parameters);
|
||||
List<ParameterBinding> bindings = getBindings(parameters);
|
||||
|
||||
return new ParameterBinder(parameters, createSetters(bindings, setterFactory));
|
||||
return new ParameterBinder(parameters, createSetters(bindings, likeFactory, setterFactory));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,9 +96,12 @@ class ParameterBinderFactory {
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
QueryParameterSetterFactory expressionSetterFactory = QueryParameterSetterFactory.parsing(parser,
|
||||
evaluationContextProvider, parameters);
|
||||
|
||||
QueryParameterSetterFactory like = QueryParameterSetterFactory.forLikeRewrite(parameters);
|
||||
QueryParameterSetterFactory basicSetterFactory = QueryParameterSetterFactory.basic(parameters);
|
||||
|
||||
return new ParameterBinder(parameters, createSetters(bindings, query, expressionSetterFactory, basicSetterFactory),
|
||||
return new ParameterBinder(parameters,
|
||||
createSetters(bindings, query, expressionSetterFactory, like, basicSetterFactory),
|
||||
!query.usesPaging());
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.function.Function;
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
|
||||
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
|
||||
import org.springframework.data.jpa.repository.query.QueryParameterSetter.NamedOrIndexedQueryParameterSetter;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
@@ -62,6 +63,20 @@ abstract class QueryParameterSetterFactory {
|
||||
return new BasicQueryParameterSetterFactory(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryParameterSetterFactory} for the given {@link JpaParameters} applying LIKE rewrite for
|
||||
* renamed {@code :foo%} or {@code %:bar} bindings.
|
||||
*
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @return a basic {@link QueryParameterSetterFactory} that can handle named parameters.
|
||||
*/
|
||||
static QueryParameterSetterFactory forLikeRewrite(JpaParameters parameters) {
|
||||
|
||||
Assert.notNull(parameters, "JpaParameters must not be null");
|
||||
|
||||
return new LikeRewritingQueryParameterSetterFactory(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryParameterSetterFactory} using the given {@link JpaParameters} and
|
||||
* {@link ParameterMetadata}.
|
||||
@@ -117,6 +132,29 @@ abstract class QueryParameterSetterFactory {
|
||||
ParameterImpl.of(parameter, binding), temporalType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static JpaParameter findParameterForBinding(Parameters<JpaParameters, JpaParameter> parameters, String name) {
|
||||
|
||||
JpaParameters bindableParameters = parameters.getBindableParameters();
|
||||
|
||||
for (JpaParameter bindableParameter : bindableParameters) {
|
||||
if (name.equals(getRequiredName(bindableParameter))) {
|
||||
return bindableParameter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getRequiredName(JpaParameter p) {
|
||||
return p.getName().orElseThrow(() -> new IllegalStateException(ParameterBinder.PARAMETER_NEEDS_TO_BE_NAMED));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Object getValue(JpaParametersParameterAccessor accessor, Parameter parameter) {
|
||||
return accessor.getValue(parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles bindings that are SpEL expressions by evaluating the expression to obtain a value.
|
||||
*
|
||||
@@ -176,6 +214,46 @@ abstract class QueryParameterSetterFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles bindings that use Like-rewriting.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.1.2
|
||||
*/
|
||||
private static class LikeRewritingQueryParameterSetterFactory extends QueryParameterSetterFactory {
|
||||
|
||||
private final Parameters<?, ?> parameters;
|
||||
|
||||
/**
|
||||
* @param parameters must not be {@literal null}.
|
||||
*/
|
||||
LikeRewritingQueryParameterSetterFactory(Parameters<?, ?> parameters) {
|
||||
|
||||
Assert.notNull(parameters, "Parameters must not be null");
|
||||
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery) {
|
||||
|
||||
if (binding.isExpression() || !(binding instanceof LikeParameterBinding likeBinding)
|
||||
|| !declaredQuery.hasNamedParameter()) {
|
||||
return null;
|
||||
}
|
||||
JpaParameter parameter = QueryParameterSetterFactory.findParameterForBinding((JpaParameters) parameters,
|
||||
likeBinding.getDeclaredName());
|
||||
|
||||
if (parameter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createSetter(values -> values.getValue(parameter), binding, parameter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts values for parameter bindings from method parameters. It handles named as well as indexed parameters.
|
||||
*
|
||||
@@ -205,7 +283,7 @@ abstract class QueryParameterSetterFactory {
|
||||
JpaParameter parameter;
|
||||
|
||||
if (declaredQuery.hasNamedParameter()) {
|
||||
parameter = findParameterForBinding(binding);
|
||||
parameter = findParameterForBinding(parameters, binding.getRequiredName());
|
||||
} else {
|
||||
|
||||
int parameterIndex = binding.getRequiredPosition() - 1;
|
||||
@@ -228,28 +306,6 @@ abstract class QueryParameterSetterFactory {
|
||||
: createSetter(values -> getValue(values, parameter), binding, parameter);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JpaParameter findParameterForBinding(ParameterBinding binding) {
|
||||
|
||||
JpaParameters bindableParameters = parameters.getBindableParameters();
|
||||
|
||||
for (JpaParameter bindableParameter : bindableParameters) {
|
||||
if (binding.getRequiredName().equals(getName(bindableParameter))) {
|
||||
return bindableParameter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getValue(JpaParametersParameterAccessor accessor, Parameter parameter) {
|
||||
return accessor.getValue(parameter);
|
||||
}
|
||||
|
||||
private static String getName(JpaParameter p) {
|
||||
return p.getName().orElseThrow(() -> new IllegalStateException(ParameterBinder.PARAMETER_NEEDS_TO_BE_NAMED));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,7 +422,7 @@ abstract class QueryParameterSetterFactory {
|
||||
@Nullable
|
||||
private static String getName(@Nullable JpaParameter parameter, ParameterBinding binding) {
|
||||
|
||||
if (parameter == null) {
|
||||
if (binding.hasName() || parameter == null) {
|
||||
return binding.getName();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,14 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
|
||||
import org.springframework.data.repository.query.SpelQueryContext;
|
||||
import org.springframework.data.repository.query.SpelQueryContext.SpelExtractor;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -233,6 +236,8 @@ class StringQuery implements DeclaredQuery {
|
||||
|
||||
int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0;
|
||||
|
||||
LikeParameterBindings likeParameterBindings = new LikeParameterBindings();
|
||||
|
||||
boolean usesJpaStyleParameters = false;
|
||||
while (matcher.find()) {
|
||||
|
||||
@@ -273,9 +278,11 @@ class StringQuery implements DeclaredQuery {
|
||||
if (parameterIndex != null) {
|
||||
checkAndRegister(new LikeParameterBinding(parameterIndex, likeType, expression), bindings);
|
||||
} else {
|
||||
checkAndRegister(new LikeParameterBinding(parameterName, likeType, expression), bindings);
|
||||
|
||||
replacement = ":" + parameterName;
|
||||
LikeParameterBinding binding = likeParameterBindings.getOrCreate(parameterName, likeType, expression);
|
||||
checkAndRegister(binding, bindings);
|
||||
|
||||
replacement = ":" + binding.getRequiredName();
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -334,43 +341,7 @@ class StringQuery implements DeclaredQuery {
|
||||
return text;
|
||||
}
|
||||
|
||||
return text.substring(0, index) + potentiallyWrapWithWildcards(replacement, substring)
|
||||
+ text.substring(index + substring.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are any pre- or post-wildcards ({@literal %}), replace them with a {@literal CONCAT} function and proper
|
||||
* wildcards as string literals. NOTE: {@literal CONCAT} appears to be a standard function across relational
|
||||
* databases as well as JPA providers.
|
||||
*
|
||||
* @param replacement
|
||||
* @param substring
|
||||
* @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) {
|
||||
|
||||
boolean wildcards = substring.startsWith("%") || substring.endsWith("%");
|
||||
|
||||
if (!wildcards) {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
StringBuilder concatWrapper = new StringBuilder("CONCAT(");
|
||||
|
||||
if (substring.startsWith("%")) {
|
||||
concatWrapper.append("'%',");
|
||||
}
|
||||
|
||||
concatWrapper.append(replacement);
|
||||
|
||||
if (substring.endsWith("%")) {
|
||||
concatWrapper.append(",'%'");
|
||||
}
|
||||
|
||||
concatWrapper.append(")");
|
||||
|
||||
return concatWrapper.toString();
|
||||
return text.substring(0, index) + replacement + text.substring(index + substring.length());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -460,6 +431,60 @@ class StringQuery implements DeclaredQuery {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to create unique parameter bindings for LIKE that can be evaluated by
|
||||
* {@code LikeRewritingQueryParameterSetterFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.1.2
|
||||
*/
|
||||
static class LikeParameterBindings {
|
||||
|
||||
private final MultiValueMap<String, LikeParameterBinding> likeBindings = new LinkedMultiValueMap<>();
|
||||
|
||||
/**
|
||||
* Get an existing or create a new {@link LikeParameterBinding} if a previously bound {@code LIKE} expression cannot
|
||||
* be reused.
|
||||
*
|
||||
* @param parameterName the parameter name as declared in the actual JPQL query.
|
||||
* @param likeType type of the LIKE expression.
|
||||
* @param expression expression content if the LIKE comparison value is provided by a SpEL expression.
|
||||
* @return the Like binding. Can return an already existing binding.
|
||||
*/
|
||||
LikeParameterBinding getOrCreate(String parameterName, Type likeType, @Nullable String expression) {
|
||||
|
||||
List<LikeParameterBinding> likeParameterBindings = likeBindings.computeIfAbsent(parameterName,
|
||||
s -> new ArrayList<>());
|
||||
LikeParameterBinding reuse = null;
|
||||
|
||||
// unique parameters only required for literals as expressions create unique parameter names
|
||||
if (expression == null) {
|
||||
for (LikeParameterBinding likeParameterBinding : likeParameterBindings) {
|
||||
|
||||
if (likeParameterBinding.type == likeType) {
|
||||
reuse = likeParameterBinding;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String declaredParameterName = parameterName;
|
||||
if (reuse != null) {
|
||||
return reuse;
|
||||
}
|
||||
|
||||
if (!likeParameterBindings.isEmpty()) {
|
||||
parameterName = parameterName + "_" + likeParameterBindings.size();
|
||||
}
|
||||
|
||||
LikeParameterBinding binding = new LikeParameterBinding(parameterName, declaredParameterName, likeType,
|
||||
expression);
|
||||
likeParameterBindings.add(binding);
|
||||
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic parameter binding with name or position information.
|
||||
*
|
||||
@@ -511,6 +536,10 @@ class StringQuery implements DeclaredQuery {
|
||||
return this.position == null && this.name != null && this.name.equals(name);
|
||||
}
|
||||
|
||||
boolean hasName() {
|
||||
return this.position == null && !ObjectUtils.isEmpty(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the binding has the given position. Will always be {@literal false} in case the
|
||||
* {@link ParameterBinding} has been set up from a name.
|
||||
@@ -519,6 +548,10 @@ class StringQuery implements DeclaredQuery {
|
||||
return position != null && this.name == null && position.equals(this.position);
|
||||
}
|
||||
|
||||
boolean hasPosition() {
|
||||
return position != null && this.name == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
@@ -665,6 +698,7 @@ class StringQuery implements DeclaredQuery {
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class LikeParameterBinding extends ParameterBinding {
|
||||
|
||||
@@ -673,35 +707,45 @@ class StringQuery implements DeclaredQuery {
|
||||
|
||||
private final Type type;
|
||||
|
||||
private final @Nullable String declaredName;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param name parameter name in the final query, must not be {@literal null} or empty.
|
||||
* @param declaredName name of the declared parameter from the original query, referring to a
|
||||
* {@link JpaParameter#getName()}, must not be {@literal null} or empty.
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
LikeParameterBinding(String name, Type type) {
|
||||
this(name, type, null);
|
||||
LikeParameterBinding(String name, String declaredName, Type type) {
|
||||
this(name, declaredName, type, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type} and parameter
|
||||
* binding input.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param name parameter name in the final query, must not be {@literal null} or empty.
|
||||
* @param declaredName name of the declared parameter from the original query, referring to a
|
||||
* {@link JpaParameter#getName()}, must not be {@literal null} or empty.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param expression may be {@literal null}.
|
||||
*/
|
||||
LikeParameterBinding(String name, Type type, @Nullable String expression) {
|
||||
LikeParameterBinding(String name, String declaredName, Type type, @Nullable String expression) {
|
||||
|
||||
super(name, null, expression);
|
||||
|
||||
Assert.hasText(name, "Name must not be null or empty");
|
||||
if (expression == null && !StringUtils.hasText(declaredName)) {
|
||||
throw new IllegalArgumentException("Declared name must not be null or empty");
|
||||
}
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
Assert.isTrue(SUPPORTED_TYPES.contains(type),
|
||||
String.format("Type must be one of %s", StringUtils.collectionToCommaDelimitedString(SUPPORTED_TYPES)));
|
||||
|
||||
this.type = type;
|
||||
this.declaredName = declaredName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -732,6 +776,7 @@ class StringQuery implements DeclaredQuery {
|
||||
String.format("Type must be one of %s", StringUtils.collectionToCommaDelimitedString(SUPPORTED_TYPES)));
|
||||
|
||||
this.type = type;
|
||||
this.declaredName = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -743,13 +788,29 @@ class StringQuery implements DeclaredQuery {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDeclaredName() {
|
||||
return declaredName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the raw value properly.
|
||||
* Prepares the given raw keyword according to the like type.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Object prepare(@Nullable Object value) {
|
||||
return PersistenceProvider.unwrapTypedParameterValue(value);
|
||||
|
||||
Object unwrapped = PersistenceProvider.unwrapTypedParameterValue(value);
|
||||
if (unwrapped == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return switch (type) {
|
||||
case STARTING_WITH -> String.format("%s%%", unwrapped);
|
||||
case ENDING_WITH -> String.format("%%%s", unwrapped);
|
||||
case CONTAINING -> String.format("%%%s%%", unwrapped);
|
||||
default -> unwrapped;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -802,6 +863,7 @@ class StringQuery implements DeclaredQuery {
|
||||
|
||||
return Type.LIKE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Metadata {
|
||||
|
||||
@@ -974,14 +974,13 @@ class UserRepositoryTests {
|
||||
assertThat(result).containsOnly(thirdUser);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-292
|
||||
@Test // DATAJPA-292, GH-3041
|
||||
void executesManualQueryWithNamedLikeExpressionCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
List<User> result = repository.findByFirstnameLikeNamed("Da");
|
||||
|
||||
assertThat(result).containsOnly(thirdUser);
|
||||
assertThat(repository.findByFirstnameLikeNamed("Da")).containsOnly(thirdUser);
|
||||
assertThat(repository.findByFirstnameLikeNamed("in")).containsOnly(fourthUser);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-231
|
||||
|
||||
@@ -32,28 +32,28 @@ class LikeBindingUnitTests {
|
||||
|
||||
private static void assertAugmentedValue(Type type, Object value) {
|
||||
|
||||
LikeParameterBinding binding = new LikeParameterBinding("foo", type);
|
||||
LikeParameterBinding binding = new LikeParameterBinding("foo", "foo", type);
|
||||
assertThat(binding.prepare("value")).isEqualTo(value);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsNullName() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding(null, Type.CONTAINING));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding(null, "", Type.CONTAINING));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsEmptyName() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding("", Type.CONTAINING));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding("", "", Type.CONTAINING));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsNullType() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding("foo", null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding("foo", "foo", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInvalidType() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding("foo", Type.SIMPLE_PROPERTY));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding("foo", "foo", Type.SIMPLE_PROPERTY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,7 +64,7 @@ class LikeBindingUnitTests {
|
||||
@Test
|
||||
void setsUpInstanceForName() {
|
||||
|
||||
LikeParameterBinding binding = new LikeParameterBinding("foo", Type.CONTAINING);
|
||||
LikeParameterBinding binding = new LikeParameterBinding("foo", "foo", Type.CONTAINING);
|
||||
|
||||
assertThat(binding.hasName("foo")).isTrue();
|
||||
assertThat(binding.hasName("bar")).isFalse();
|
||||
@@ -84,4 +84,14 @@ class LikeBindingUnitTests {
|
||||
assertThat(binding.hasPosition(1)).isTrue();
|
||||
assertThat(binding.getType()).isEqualTo(Type.CONTAINING);
|
||||
}
|
||||
|
||||
@Test
|
||||
void augmentsValueCorrectly() {
|
||||
|
||||
assertAugmentedValue(Type.CONTAINING, "%value%");
|
||||
assertAugmentedValue(Type.ENDING_WITH, "%value");
|
||||
assertAugmentedValue(Type.STARTING_WITH, "value%");
|
||||
|
||||
assertThat(new LikeParameterBinding(1, Type.CONTAINING).prepare(null)).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.data.repository.query.parser.Part.Type;
|
||||
* @author Nils Borrmann
|
||||
* @author Andriy Redko
|
||||
* @author Diego Krupitza
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class StringQueryUnitTests {
|
||||
|
||||
@@ -65,7 +66,7 @@ class StringQueryUnitTests {
|
||||
|
||||
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)");
|
||||
.isEqualTo("select u from User u where u.firstname like ?1 or u.lastname like ?2");
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings).hasSize(2);
|
||||
@@ -87,7 +88,7 @@ class StringQueryUnitTests {
|
||||
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 :firstname");
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings).hasSize(1);
|
||||
@@ -98,6 +99,55 @@ class StringQueryUnitTests {
|
||||
assertThat(binding.getType()).isEqualTo(Type.ENDING_WITH);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-292
|
||||
void rewritesNamedLikeToUniqueParametersIfNecessary() {
|
||||
|
||||
StringQuery query = new StringQuery(
|
||||
"select u from User u where u.firstname like %:firstname or u.firstname like :firstname%", true);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString())
|
||||
.isEqualTo("select u from User u where u.firstname like :firstname or u.firstname like :firstname_1");
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings).hasSize(2);
|
||||
|
||||
LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0);
|
||||
assertThat(binding).isNotNull();
|
||||
assertThat(binding.hasName("firstname")).isTrue();
|
||||
assertThat(binding.getType()).isEqualTo(Type.ENDING_WITH);
|
||||
|
||||
binding = (LikeParameterBinding) bindings.get(1);
|
||||
assertThat(binding).isNotNull();
|
||||
assertThat(binding.hasName("firstname_1")).isTrue();
|
||||
assertThat(binding.getType()).isEqualTo(Type.STARTING_WITH);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-292
|
||||
void reusesLikeBindingsWherePossible() {
|
||||
|
||||
StringQuery query = new StringQuery(
|
||||
"select u from User u where u.firstname like %:firstname or u.firstname like %:firstname% or u.firstname like %:firstname% or u.firstname like %:firstname",
|
||||
true);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString()).isEqualTo(
|
||||
"select u from User u where u.firstname like :firstname or u.firstname like :firstname_1 or u.firstname like :firstname_1 or u.firstname like :firstname");
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings).hasSize(2);
|
||||
|
||||
LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0);
|
||||
assertThat(binding).isNotNull();
|
||||
assertThat(binding.hasName("firstname")).isTrue();
|
||||
assertThat(binding.getType()).isEqualTo(Type.ENDING_WITH);
|
||||
|
||||
binding = (LikeParameterBinding) bindings.get(1);
|
||||
assertThat(binding).isNotNull();
|
||||
assertThat(binding.hasName("firstname_1")).isTrue();
|
||||
assertThat(binding.getType()).isEqualTo(Type.CONTAINING);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-461
|
||||
void detectsNamedInParameterBindings() {
|
||||
|
||||
@@ -199,9 +249,8 @@ class StringQueryUnitTests {
|
||||
assertNamedBinding(LikeParameterBinding.class, "escapedWord", bindings.get(0));
|
||||
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");
|
||||
assertThat(query.getQueryString()).isEqualTo("SELECT a FROM Article a WHERE a.overview LIKE :escapedWord ESCAPE '~'"
|
||||
+ " OR a.content LIKE :escapedWord ESCAPE '~' OR a.title = :word ORDER BY a.articleId DESC");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-483
|
||||
@@ -275,6 +324,17 @@ class StringQueryUnitTests {
|
||||
assertThat(queryString).isEqualTo("select a from A a where a.b in :__$synthetic$__1 and a.c in :__$synthetic$__2");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-712
|
||||
void shouldReplaceExpressionWithLikeParameters() {
|
||||
|
||||
StringQuery query = new StringQuery(
|
||||
"select a from A a where a.b LIKE :#{#filter.login}% and a.c LIKE %:#{#filter.login}", true);
|
||||
String queryString = query.getQueryString();
|
||||
|
||||
assertThat(queryString)
|
||||
.isEqualTo("select a from A a where a.b LIKE :__$synthetic$__1 and a.c LIKE :__$synthetic$__2");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-712
|
||||
void shouldReplaceAllPositionExpressionParametersWithInClause() {
|
||||
|
||||
|
||||
@@ -164,8 +164,8 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
|
||||
@Query("select u from User u where u.firstname like ?1%")
|
||||
List<User> findByFirstnameLike(String firstname);
|
||||
|
||||
// DATAJPA-292
|
||||
@Query("select u from User u where u.firstname like :firstname%")
|
||||
// DATAJPA-292, GH-3041
|
||||
@Query("select u from User u where u.firstname like :firstname% or u.firstname like %:firstname")
|
||||
List<User> findByFirstnameLikeNamed(@Param("firstname") String firstname);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user