diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java index 4821865de..670e030fb 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java @@ -71,9 +71,9 @@ interface DeclaredQuery { boolean isDefaultProjection(); /** - * Returns the {@link StringQuery.ParameterBinding}s registered. + * Returns the {@link ParameterBinding}s registered. */ - List getParameterBindings(); + List getParameterBindings(); /** * Creates a new {@literal DeclaredQuery} representing a count query, i.e. a query returning the number of rows to be diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java index dd37c4508..dbb3be8ba 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java @@ -60,7 +60,7 @@ class EmptyDeclaredQuery implements DeclaredQuery { } @Override - public List getParameterBindings() { + public List getParameterBindings() { return Collections.emptyList(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java index 6cdd559f3..7d10ead1e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java @@ -19,8 +19,9 @@ import java.util.ArrayList; import java.util.List; import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; +import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier; +import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; -import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; @@ -46,11 +47,10 @@ class ParameterBinderFactory { Assert.notNull(parameters, "JpaParameters must not be null"); - QueryParameterSetterFactory likeFactory = QueryParameterSetterFactory.forLikeRewrite(parameters); QueryParameterSetterFactory setterFactory = QueryParameterSetterFactory.basic(parameters); List bindings = getBindings(parameters); - return new ParameterBinder(parameters, createSetters(bindings, likeFactory, setterFactory)); + return new ParameterBinder(parameters, createSetters(bindings, setterFactory)); } /** @@ -97,11 +97,9 @@ class ParameterBinderFactory { 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, like, basicSetterFactory), + return new ParameterBinder(parameters, createSetters(bindings, query, expressionSetterFactory, basicSetterFactory), !query.usesPaging()); } @@ -113,7 +111,11 @@ class ParameterBinderFactory { for (JpaParameter parameter : parameters) { if (parameter.isBindable()) { - result.add(new ParameterBinding(++bindableParameterIndex)); + int index = ++bindableParameterIndex; + BindingIdentifier bindingIdentifier = parameter.getName().map(it -> BindingIdentifier.of(it, index)) + .orElseGet(() -> BindingIdentifier.of(index)); + + result.add(new ParameterBinding(bindingIdentifier, ParameterOrigin.ofParameter(bindingIdentifier))); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java new file mode 100644 index 000000000..bc0086fab --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java @@ -0,0 +1,582 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.util.ObjectUtils.*; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; +import org.springframework.data.repository.query.parser.Part.Type; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * A generic parameter binding with name or position information. + * + * @author Thomas Darimont + * @author Mark Paluch + */ +class ParameterBinding { + + private final BindingIdentifier identifier; + private final ParameterOrigin origin; + + /** + * Creates a new {@link ParameterBinding} for the parameter with the given identifier and origin. + * + * @param identifier of the parameter, must not be {@literal null}. + * @param origin the origin of the parameter (expression or method argument) + */ + ParameterBinding(BindingIdentifier identifier, ParameterOrigin origin) { + + Assert.notNull(identifier, "BindingIdentifier must not be null"); + Assert.notNull(origin, "ParameterOrigin must not be null"); + + this.identifier = identifier; + this.origin = origin; + } + + public BindingIdentifier getIdentifier() { + return identifier; + } + + public ParameterOrigin getOrigin() { + return origin; + } + + /** + * @return the name if available or {@literal null}. + */ + @Nullable + public String getName() { + return identifier.hasName() ? identifier.getName() : null; + } + + /** + * @return the name + * @throws IllegalStateException if the name is not available. + * @since 2.0 + */ + String getRequiredName() throws IllegalStateException { + + String name = getName(); + + if (name != null) { + return name; + } + + throw new IllegalStateException(String.format("Required name for %s not available", this)); + } + + /** + * @return the position if available or {@literal null}. + */ + @Nullable + Integer getPosition() { + return identifier.hasPosition() ? identifier.getPosition() : null; + } + + /** + * @return the position + * @throws IllegalStateException if the position is not available. + * @since 2.0 + */ + int getRequiredPosition() throws IllegalStateException { + + Integer position = getPosition(); + + if (position != null) { + return position; + } + + throw new IllegalStateException(String.format("Required position for %s not available", this)); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ParameterBinding that = (ParameterBinding) o; + + if (!nullSafeEquals(identifier, that.identifier)) { + return false; + } + return nullSafeEquals(origin, that.origin); + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(identifier); + result = 31 * result + nullSafeHashCode(origin); + return result; + } + + @Override + public String toString() { + return String.format("ParameterBinding [identifier: %s, origin: %s]", identifier, origin); + } + + /** + * @param valueToBind value to prepare + */ + @Nullable + public Object prepare(@Nullable Object valueToBind) { + return valueToBind; + } + + /** + * Check whether the {@code other} binding uses the same bind target. + * + * @param other + * @return + */ + public boolean bindsTo(ParameterBinding other) { + + if (identifier.hasName() && other.identifier.hasName()) { + if (identifier.getName().equals(other.identifier.getName())) { + return true; + } + } + + if (identifier.hasPosition() && other.identifier.hasPosition()) { + if (identifier.getPosition() == other.identifier.getPosition()) { + return true; + } + } + + return false; + } + + /** + * Represents a {@link ParameterBinding} in a JPQL query augmented with instructions of how to apply a parameter as an + * {@code IN} parameter. + * + * @author Thomas Darimont + */ + static class InParameterBinding extends ParameterBinding { + + /** + * Creates a new {@link InParameterBinding} for the parameter with the given name. + */ + InParameterBinding(BindingIdentifier identifier, ParameterOrigin origin) { + super(identifier, origin); + } + + @Override + public Object prepare(@Nullable Object value) { + + if (!ObjectUtils.isArray(value)) { + return value; + } + + int length = Array.getLength(value); + Collection result = new ArrayList<>(length); + + for (int i = 0; i < length; i++) { + result.add(Array.get(value, i)); + } + + return result; + } + } + + /** + * Represents a parameter binding in a JPQL query augmented with instructions of how to apply a parameter as LIKE + * parameter. This allows expressions like {@code …like %?1} in the JPQL query, which is not allowed by plain JPA. + * + * @author Oliver Gierke + * @author Thomas Darimont + */ + static class LikeParameterBinding extends ParameterBinding { + + private static final List SUPPORTED_TYPES = Arrays.asList(Type.CONTAINING, Type.STARTING_WITH, + Type.ENDING_WITH, Type.LIKE); + + private final Type type; + + /** + * Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type} and parameter + * binding input. + * + * @param identifier must not be {@literal null} or empty. + * @param type must not be {@literal null}. + */ + LikeParameterBinding(BindingIdentifier identifier, ParameterOrigin origin, Type type) { + + super(identifier, origin); + + 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; + } + + /** + * Returns the {@link Type} of the binding. + * + * @return the type + */ + public Type getType() { + return type; + } + + /** + * Extracts the raw value properly. + */ + @Nullable + @Override + public Object prepare(@Nullable Object 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 + public boolean equals(Object obj) { + + if (!(obj instanceof LikeParameterBinding)) { + return false; + } + + LikeParameterBinding that = (LikeParameterBinding) obj; + + return super.equals(obj) && this.type.equals(that.type); + } + + @Override + public int hashCode() { + + int result = super.hashCode(); + + result += nullSafeHashCode(this.type); + + return result; + } + + @Override + public String toString() { + return String.format("LikeBinding [identifier: %s, origin: %s, type: %s]", getIdentifier(), getOrigin(), + getType()); + } + + /** + * Extracts the like {@link Type} from the given JPA like expression. + * + * @param expression must not be {@literal null} or empty. + */ + static Type getLikeTypeFrom(String expression) { + + Assert.hasText(expression, "Expression must not be null or empty"); + + if (expression.matches("%.*%")) { + return Type.CONTAINING; + } + + if (expression.startsWith("%")) { + return Type.ENDING_WITH; + } + + if (expression.endsWith("%")) { + return Type.STARTING_WITH; + } + + return Type.LIKE; + } + } + + static class ParameterImpl implements jakarta.persistence.Parameter { + + private final BindingIdentifier identifier; + private final Class parameterType; + + /** + * Creates a new {@link ParameterImpl} for the given {@link JpaParameter} and {@link ParameterBinding}. + * + * @param parameter can be {@literal null}. + * @param binding must not be {@literal null}. + * @return a {@link jakarta.persistence.Parameter} object based on the information from the arguments. + */ + static jakarta.persistence.Parameter of(@Nullable JpaParameter parameter, ParameterBinding binding) { + + Class type = parameter == null ? Object.class : parameter.getType(); + + return new ParameterImpl<>(binding.getIdentifier(), type); + } + + public ParameterImpl(BindingIdentifier identifier, Class parameterType) { + this.identifier = identifier; + this.parameterType = parameterType; + } + + @Nullable + @Override + public String getName() { + return identifier.hasName() ? identifier.getName() : null; + } + + @Nullable + @Override + public Integer getPosition() { + return identifier.hasPosition() ? identifier.getPosition() : null; + } + + @Override + public Class getParameterType() { + return parameterType; + } + + } + + /** + * Identifies a binding parameter by name, position or both. Used to bind parameters to a query or to describe a + * {@link MethodInvocationArgument} origin. + */ + sealed interface BindingIdentifier permits Named,Indexed,NamedAndIndexed { + + /** + * Creates an identifier for the given {@code name}. + * + * @param name + * @return + */ + static BindingIdentifier of(String name) { + + Assert.hasText(name, "Name must not be empty"); + + return new Named(name); + } + + /** + * Creates an identifier for the given {@code position}. + * + * @param position 1-based index. + * @return + */ + static BindingIdentifier of(int position) { + + Assert.isTrue(position > 0, "Index position must be greater zero"); + + return new Indexed(position); + } + + /** + * Creates an identifier for the given {@code name} and {@code position}. + * + * @param name + * @return + */ + static BindingIdentifier of(String name, int position) { + + Assert.hasText(name, "Name must not be empty"); + + return new NamedAndIndexed(name, position); + } + + /** + * @return {@code true} if the binding is associated with a name. + */ + default boolean hasName() { + return false; + } + + /** + * @return {@code true} if the binding is associated with a position index. + */ + default boolean hasPosition() { + return false; + } + + /** + * Returns the binding name {@link #hasName() if present} or throw {@link IllegalStateException} if no name + * associated. + * + * @return the binding name. + */ + default String getName() { + throw new IllegalStateException("No name associated"); + } + + /** + * Returns the binding name {@link #hasPosition() if present} or throw {@link IllegalStateException} if no position + * associated. + * + * @return the binding position. + */ + default int getPosition() { + throw new IllegalStateException("No position associated"); + } + } + + private record Named(String name) implements BindingIdentifier { + + @Override + public boolean hasName() { + return true; + } + + @Override + public String getName() { + return name(); + } + } + + private record Indexed(int position) implements BindingIdentifier { + + @Override + public boolean hasPosition() { + return true; + } + + @Override + public int getPosition() { + return position(); + } + } + + private record NamedAndIndexed(String name, int position) implements BindingIdentifier { + + @Override + public boolean hasName() { + return true; + } + + @Override + public String getName() { + return name(); + } + + @Override + public boolean hasPosition() { + return true; + } + + @Override + public int getPosition() { + return position(); + } + } + + /** + * Value type hierarchy to describe where a binding parameter comes from, either method call or an expression. + */ + sealed interface ParameterOrigin permits Expression,MethodInvocationArgument { + + /** + * Creates a {@link Expression} for the given {@code expression} string. + * + * @param expression must not be {@literal null}. + * @return {@link Expression} for the given {@code expression} string. + */ + static Expression ofExpression(String expression) { + return new Expression(expression); + } + + /** + * Creates a {@link MethodInvocationArgument} object for {@code name} and {@code position}. Either the name or the + * position must be given, + * + * @param name the parameter name from the method invocation, can be {@literal null}. + * @param position the parameter position (1-based) from the method invocation, can be {@literal null}. + * @return {@link MethodInvocationArgument} object for {@code name} and {@code position} + */ + static MethodInvocationArgument ofParameter(@Nullable String name, @Nullable Integer position) { + + BindingIdentifier identifier; + if (!ObjectUtils.isEmpty(name) && position != null) { + identifier = BindingIdentifier.of(name, position); + } else if (!ObjectUtils.isEmpty(name)) { + identifier = BindingIdentifier.of(name); + } else { + identifier = BindingIdentifier.of(position); + } + + return ofParameter(identifier); + } + + /** + * Creates a {@link MethodInvocationArgument} using {@link BindingIdentifier}. + * + * @param identifier must not be {@literal null}. + * @return {@link MethodInvocationArgument} for {@link BindingIdentifier}. + */ + static MethodInvocationArgument ofParameter(BindingIdentifier identifier) { + + return new MethodInvocationArgument(identifier); + } + + boolean isMethodArgument(); + + boolean isExpression(); + } + + /** + * Value object capturing the expression of which a binding parameter originates. + * + * @param expression + */ + public record Expression(String expression) implements ParameterOrigin { + + @Override + public boolean isMethodArgument() { + return false; + } + + @Override + public boolean isExpression() { + return true; + } + } + + /** + * Value object capturing the method invocation parameter reference. + * + * @param identifier + */ + public record MethodInvocationArgument(BindingIdentifier identifier) implements ParameterOrigin { + + @Override + public boolean isMethodArgument() { + return true; + } + + @Override + public boolean isExpression() { + return false; + } + } +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java index a1991f1a3..261deb4b2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java @@ -109,7 +109,7 @@ interface QueryParameterSetter { } else { - final Object value = valueExtractor.apply(accessor); + Object value = valueExtractor.apply(accessor); if (parameter instanceof ParameterExpression) { errorHandling.execute(() -> query.setParameter((Parameter) parameter, value)); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java index f2d35b84e..0ea048f32 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java @@ -22,10 +22,11 @@ import java.util.List; import java.util.function.Function; import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; +import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier; +import org.springframework.data.jpa.repository.query.ParameterBinding.MethodInvocationArgument; +import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterImpl; 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; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; @@ -63,20 +64,6 @@ 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}. @@ -133,7 +120,7 @@ abstract class QueryParameterSetterFactory { } @Nullable - private static JpaParameter findParameterForBinding(Parameters parameters, String name) { + static JpaParameter findParameterForBinding(Parameters parameters, String name) { JpaParameters bindableParameters = parameters.getBindableParameters(); @@ -150,9 +137,20 @@ abstract class QueryParameterSetterFactory { return p.getName().orElseThrow(() -> new IllegalStateException(ParameterBinder.PARAMETER_NEEDS_TO_BE_NAMED)); } - @Nullable - static Object getValue(JpaParametersParameterAccessor accessor, Parameter parameter) { - return accessor.getValue(parameter); + static JpaParameter findParameterForBinding(Parameters parameters, int parameterIndex) { + + JpaParameters bindableParameters = parameters.getBindableParameters(); + + Assert.isTrue( // + parameterIndex < bindableParameters.getNumberOfParameters(), // + () -> String.format( // + "At least %s parameter(s) provided but only %s parameter(s) present in query", // + parameterIndex + 1, // + bindableParameters.getNumberOfParameters() // + ) // + ); + + return bindableParameters.getParameter(parameterIndex); } /** @@ -189,11 +187,11 @@ abstract class QueryParameterSetterFactory { @Override public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery) { - if (!binding.isExpression()) { + if (!(binding.getOrigin()instanceof ParameterBinding.Expression e)) { return null; } - Expression expression = parser.parseExpression(binding.getExpression()); + Expression expression = parser.parseExpression(e.expression()); return createSetter(values -> evaluateExpression(expression, values), binding, null); } @@ -214,51 +212,12 @@ 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. * * @author Jens Schauder * @author Oliver Gierke + * @author Mark Paluch * @since 2.0 */ private static class BasicQueryParameterSetterFactory extends QueryParameterSetterFactory { @@ -281,24 +240,16 @@ abstract class QueryParameterSetterFactory { Assert.notNull(binding, "Binding must not be null"); JpaParameter parameter; + if (!(binding.getOrigin()instanceof MethodInvocationArgument mia)) { + return QueryParameterSetter.NOOP; + } + + BindingIdentifier identifier = mia.identifier(); if (declaredQuery.hasNamedParameter()) { - parameter = findParameterForBinding(parameters, binding.getRequiredName()); + parameter = findParameterForBinding(parameters, identifier.getName()); } else { - - int parameterIndex = binding.getRequiredPosition() - 1; - JpaParameters bindableParameters = parameters.getBindableParameters(); - - Assert.isTrue( // - parameterIndex < bindableParameters.getNumberOfParameters(), // - () -> String.format( // - "At least %s parameter(s) provided but only %s parameter(s) present in query", // - binding.getRequiredPosition(), // - bindableParameters.getNumberOfParameters() // - ) // - ); - - parameter = bindableParameters.getParameter(binding.getRequiredPosition() - 1); + parameter = findParameterForBinding(parameters, identifier.getPosition() - 1); } return parameter == null // @@ -306,6 +257,10 @@ abstract class QueryParameterSetterFactory { : createSetter(values -> getValue(values, parameter), binding, parameter); } + @Nullable + private Object getValue(JpaParametersParameterAccessor accessor, Parameter parameter) { + return accessor.getValue(parameter); + } } /** @@ -368,67 +323,4 @@ abstract class QueryParameterSetterFactory { } } - private static class ParameterImpl implements jakarta.persistence.Parameter { - - private final Class parameterType; - private final @Nullable String name; - private final @Nullable Integer position; - - /** - * Creates a new {@link ParameterImpl} for the given {@link JpaParameter} and {@link ParameterBinding}. - * - * @param parameter can be {@literal null}. - * @param binding must not be {@literal null}. - * @return a {@link jakarta.persistence.Parameter} object based on the information from the arguments. - */ - static jakarta.persistence.Parameter of(@Nullable JpaParameter parameter, ParameterBinding binding) { - - Class type = parameter == null ? Object.class : parameter.getType(); - - return new ParameterImpl<>(type, getName(parameter, binding), binding.getPosition()); - } - - /** - * Creates a new {@link ParameterImpl} for the given name, position and parameter type. - * - * @param parameterType must not be {@literal null}. - * @param name can be {@literal null}. - * @param position can be {@literal null}. - */ - private ParameterImpl(Class parameterType, @Nullable String name, @Nullable Integer position) { - - this.name = name; - this.position = position; - this.parameterType = parameterType; - } - - @Nullable - @Override - public String getName() { - return name; - } - - @Nullable - @Override - public Integer getPosition() { - return position; - } - - @Override - public Class getParameterType() { - return parameterType; - } - - @Nullable - private static String getName(@Nullable JpaParameter parameter, ParameterBinding binding) { - - if (binding.hasName() || parameter == null) { - return binding.getName(); - } - - return parameter.isNamedParameter() // - ? parameter.getName().orElseThrow(() -> new IllegalArgumentException("o_O parameter needs to have a name")) // - : null; - } - } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index b3b8a3549..8d82fac55 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -15,21 +15,16 @@ */ package org.springframework.data.jpa.repository.query; -import static java.util.regex.Pattern.CASE_INSENSITIVE; -import static org.springframework.util.ObjectUtils.nullSafeEquals; -import static org.springframework.util.ObjectUtils.nullSafeHashCode; - -import java.lang.reflect.Array; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.function.BiFunction; 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.jpa.repository.query.ParameterBinding.BindingIdentifier; +import org.springframework.data.jpa.repository.query.ParameterBinding.InParameterBinding; +import org.springframework.data.jpa.repository.query.ParameterBinding.LikeParameterBinding; +import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin; import org.springframework.data.repository.query.SpelQueryContext; import org.springframework.data.repository.query.SpelQueryContext.SpelExtractor; import org.springframework.data.repository.query.parser.Part.Type; @@ -40,6 +35,8 @@ import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import static java.util.regex.Pattern.*; + /** * Encapsulation of a JPA query String. Offers access to parameters as bindings. The internal query String is cleaned * from decorated parameters like {@literal %:lastname%} and the matching bindings take care of applying the decorations @@ -143,7 +140,7 @@ class StringQuery implements DeclaredQuery { @Override public boolean hasNamedParameter() { - return bindings.stream().anyMatch(b -> b.getName() != null); + return bindings.stream().anyMatch(b -> b.getIdentifier().hasName()); } @Override @@ -269,18 +266,28 @@ class StringQuery implements DeclaredQuery { throw new IllegalArgumentException("Mixing of ? parameters and other forms like ?1 is not supported"); } + BindingIdentifier identifier; + if (parameterIndex != null) { + identifier = BindingIdentifier.of(parameterIndex); + } else { + identifier = BindingIdentifier.of(parameterName); + } + ParameterOrigin origin = ObjectUtils.isEmpty(expression) + ? ParameterOrigin.ofParameter(parameterName, parameterIndex) + : ParameterOrigin.ofExpression(expression); + switch (ParameterBindingType.of(typeSource)) { case LIKE: - Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2)); + Type likeType = ParameterBinding.LikeParameterBinding.getLikeTypeFrom(matcher.group(2)); replacement = matcher.group(3); if (parameterIndex != null) { - checkAndRegister(new LikeParameterBinding(parameterIndex, likeType, expression), bindings); + checkAndRegister(new LikeParameterBinding(identifier, origin, likeType), bindings); } else { - LikeParameterBinding binding = likeParameterBindings.getOrCreate(parameterName, likeType, expression); + LikeParameterBinding binding = likeParameterBindings.getOrCreate(parameterName, likeType, origin); checkAndRegister(binding, bindings); replacement = ":" + binding.getRequiredName(); @@ -290,20 +297,14 @@ class StringQuery implements DeclaredQuery { case IN: - if (parameterIndex != null) { - checkAndRegister(new InParameterBinding(parameterIndex, expression), bindings); - } else { - checkAndRegister(new InParameterBinding(parameterName, expression), bindings); - } + checkAndRegister(new InParameterBinding(identifier, origin), bindings); break; case AS_IS: // fall-through we don't need a special parameter binding for the given parameter. default: - bindings.add(parameterIndex != null // - ? new ParameterBinding(null, parameterIndex, expression) // - : new ParameterBinding(parameterName, null, expression)); + bindings.add(new ParameterBinding(identifier, origin)); } if (replacement != null) { @@ -374,7 +375,7 @@ class StringQuery implements DeclaredQuery { private static void checkAndRegister(ParameterBinding binding, List bindings) { bindings.stream() // - .filter(it -> it.hasName(binding.getName()) || it.hasPosition(binding.getPosition())) // + .filter(it -> it.bindsTo(binding)) // .forEach(it -> Assert.isTrue(it.equals(binding), String.format(MESSAGE, it, binding))); if (!bindings.contains(binding)) { @@ -432,6 +433,10 @@ class StringQuery implements DeclaredQuery { } } + private static class Metadata { + private boolean usesJdbcStyleParameters = false; + } + /** * Utility to create unique parameter bindings for LIKE that can be evaluated by * {@code LikeRewritingQueryParameterSetterFactory}. @@ -449,27 +454,26 @@ class StringQuery implements DeclaredQuery { * * @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. + * @param origin origin of the parameter. * @return the Like binding. Can return an already existing binding. */ - LikeParameterBinding getOrCreate(String parameterName, Type likeType, @Nullable String expression) { + LikeParameterBinding getOrCreate(String parameterName, Type likeType, ParameterOrigin origin) { List 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) { + if (origin.isMethodArgument()) { for (LikeParameterBinding likeParameterBinding : likeParameterBindings) { - if (likeParameterBinding.type == likeType) { + if (likeParameterBinding.getType() == likeType) { reuse = likeParameterBinding; break; } } } - String declaredParameterName = parameterName; if (reuse != null) { return reuse; } @@ -478,396 +482,10 @@ class StringQuery implements DeclaredQuery { parameterName = parameterName + "_" + likeParameterBindings.size(); } - LikeParameterBinding binding = new LikeParameterBinding(parameterName, declaredParameterName, likeType, - expression); + LikeParameterBinding binding = new LikeParameterBinding(BindingIdentifier.of(parameterName), origin, likeType); likeParameterBindings.add(binding); return binding; } } - - /** - * A generic parameter binding with name or position information. - * - * @author Thomas Darimont - */ - static class ParameterBinding { - - private final @Nullable String name; - private final @Nullable String expression; - private final @Nullable Integer position; - - /** - * Creates a new {@link ParameterBinding} for the parameter with the given position. - * - * @param position must not be {@literal null}. - */ - ParameterBinding(Integer position) { - this(null, position, null); - } - - /** - * Creates a new {@link ParameterBinding} for the parameter with the given name, position and expression - * information. Either {@literal name} or {@literal position} must be not {@literal null}. - * - * @param name of the parameter may be {@literal null}. - * @param position of the parameter may be {@literal null}. - * @param expression the expression to apply to any value for this parameter. - */ - ParameterBinding(@Nullable String name, @Nullable Integer position, @Nullable String expression) { - - if (name == null) { - Assert.notNull(position, "Position must not be null"); - } - - if (position == null) { - Assert.notNull(name, "Name must not be null"); - } - - this.name = name; - this.position = position; - this.expression = expression; - } - - /** - * Returns whether the binding has the given name. Will always be {@literal false} in case the - * {@link ParameterBinding} has been set up from a position. - */ - boolean hasName(@Nullable String name) { - 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. - */ - boolean hasPosition(@Nullable Integer position) { - return position != null && this.name == null && position.equals(this.position); - } - - boolean hasPosition() { - return position != null && this.name == null; - } - - /** - * @return the name - */ - @Nullable - public String getName() { - return name; - } - - /** - * @return the name - * @throws IllegalStateException if the name is not available. - * @since 2.0 - */ - String getRequiredName() throws IllegalStateException { - - String name = getName(); - - if (name != null) { - return name; - } - - throw new IllegalStateException(String.format("Required name for %s not available", this)); - } - - /** - * @return the position - */ - @Nullable - Integer getPosition() { - return position; - } - - /** - * @return the position - * @throws IllegalStateException if the position is not available. - * @since 2.0 - */ - int getRequiredPosition() throws IllegalStateException { - - Integer position = getPosition(); - - if (position != null) { - return position; - } - - throw new IllegalStateException(String.format("Required position for %s not available", this)); - } - - /** - * @return {@literal true} if this parameter binding is a synthetic SpEL expression. - */ - public boolean isExpression() { - return this.expression != null; - } - - @Override - public int hashCode() { - - int result = 17; - - result += nullSafeHashCode(this.name); - result += nullSafeHashCode(this.position); - result += nullSafeHashCode(this.expression); - - return result; - } - - @Override - public boolean equals(Object obj) { - - if (!(obj instanceof ParameterBinding)) { - return false; - } - - ParameterBinding that = (ParameterBinding) obj; - - return nullSafeEquals(this.name, that.name) && nullSafeEquals(this.position, that.position) - && nullSafeEquals(this.expression, that.expression); - } - - @Override - public String toString() { - return String.format("ParameterBinding [name: %s, position: %d, expression: %s]", getName(), getPosition(), - getExpression()); - } - - /** - * @param valueToBind value to prepare - */ - @Nullable - public Object prepare(@Nullable Object valueToBind) { - return valueToBind; - } - - @Nullable - public String getExpression() { - return expression; - } - } - - /** - * Represents a {@link ParameterBinding} in a JPQL query augmented with instructions of how to apply a parameter as an - * {@code IN} parameter. - * - * @author Thomas Darimont - */ - static class InParameterBinding extends ParameterBinding { - - /** - * Creates a new {@link InParameterBinding} for the parameter with the given name. - */ - InParameterBinding(String name, @Nullable String expression) { - super(name, null, expression); - } - - /** - * Creates a new {@link InParameterBinding} for the parameter with the given position. - */ - InParameterBinding(int position, @Nullable String expression) { - super(null, position, expression); - } - - @Override - public Object prepare(@Nullable Object value) { - - if (!ObjectUtils.isArray(value)) { - return value; - } - - int length = Array.getLength(value); - Collection result = new ArrayList<>(length); - - for (int i = 0; i < length; i++) { - result.add(Array.get(value, i)); - } - - return result; - } - } - - /** - * Represents a parameter binding in a JPQL query augmented with instructions of how to apply a parameter as LIKE - * parameter. This allows expressions like {@code …like %?1} in the JPQL query, which is not allowed by plain JPA. - * - * @author Oliver Gierke - * @author Thomas Darimont - * @author Mark Paluch - */ - static class LikeParameterBinding extends ParameterBinding { - - private static final List SUPPORTED_TYPES = Arrays.asList(Type.CONTAINING, Type.STARTING_WITH, - Type.ENDING_WITH, Type.LIKE); - - 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 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, 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 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, 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; - } - - /** - * Creates a new {@link LikeParameterBinding} for the parameter with the given position and {@link Type}. - * - * @param position position of the parameter in the query. - * @param type must not be {@literal null}. - */ - LikeParameterBinding(int position, Type type) { - this(position, type, null); - } - - /** - * Creates a new {@link LikeParameterBinding} for the parameter with the given position and {@link Type}. - * - * @param position position of the parameter in the query. - * @param type must not be {@literal null}. - * @param expression may be {@literal null}. - */ - LikeParameterBinding(int position, Type type, @Nullable String expression) { - - super(null, position, expression); - - Assert.isTrue(position > 0, "Position must be greater than zero"); - 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 = null; - } - - /** - * Returns the {@link Type} of the binding. - * - * @return the type - */ - public Type getType() { - return type; - } - - @Nullable - public String getDeclaredName() { - return declaredName; - } - - /** - * Prepares the given raw keyword according to the like type. - */ - @Nullable - @Override - public Object prepare(@Nullable Object 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 - public boolean equals(Object obj) { - - if (!(obj instanceof LikeParameterBinding)) { - return false; - } - - LikeParameterBinding that = (LikeParameterBinding) obj; - - return super.equals(obj) && this.type.equals(that.type); - } - - @Override - public int hashCode() { - - int result = super.hashCode(); - - result += nullSafeHashCode(this.type); - - return result; - } - - @Override - public String toString() { - return String.format("LikeBinding [name: %s, position: %d, type: %s]", getName(), getPosition(), type); - } - - /** - * Extracts the like {@link Type} from the given JPA like expression. - * - * @param expression must not be {@literal null} or empty. - */ - private static Type getLikeTypeFrom(String expression) { - - Assert.hasText(expression, "Expression must not be null or empty"); - - if (expression.matches("%.*%")) { - return Type.CONTAINING; - } - - if (expression.startsWith("%")) { - return Type.ENDING_WITH; - } - - if (expression.endsWith("%")) { - return Type.STARTING_WITH; - } - - return Type.LIKE; - } - - } - - static class Metadata { - private boolean usesJdbcStyleParameters = false; - } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java index d94779b2c..f07f9d7e8 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/LikeBindingUnitTests.java @@ -18,7 +18,9 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; import org.junit.jupiter.api.Test; -import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding; +import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier; +import org.springframework.data.jpa.repository.query.ParameterBinding.LikeParameterBinding; +import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin; import org.springframework.data.repository.query.parser.Part.Type; /** @@ -32,57 +34,39 @@ class LikeBindingUnitTests { private static void assertAugmentedValue(Type type, Object value) { - LikeParameterBinding binding = new LikeParameterBinding("foo", "foo", type); + LikeParameterBinding binding = new LikeParameterBinding(BindingIdentifier.of("foo"), + ParameterOrigin.ofExpression("foo"), type); assertThat(binding.prepare("value")).isEqualTo(value); } @Test void rejectsNullName() { - assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding(null, "", Type.CONTAINING)); + assertThatIllegalArgumentException() + .isThrownBy(() -> new LikeParameterBinding(null, ParameterOrigin.ofExpression(""), Type.CONTAINING)); } @Test void rejectsEmptyName() { - assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding("", "", Type.CONTAINING)); + assertThatIllegalArgumentException().isThrownBy( + () -> new LikeParameterBinding(BindingIdentifier.of(""), ParameterOrigin.ofExpression(""), Type.CONTAINING)); } @Test void rejectsNullType() { - assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding("foo", "foo", null)); + assertThatIllegalArgumentException().isThrownBy( + () -> new LikeParameterBinding(BindingIdentifier.of("foo"), ParameterOrigin.ofExpression("foo"), null)); } @Test void rejectsInvalidType() { - assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding("foo", "foo", Type.SIMPLE_PROPERTY)); + assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding(BindingIdentifier.of("foo"), + ParameterOrigin.ofExpression("foo"), Type.SIMPLE_PROPERTY)); } @Test void rejectsInvalidPosition() { - assertThatIllegalArgumentException().isThrownBy(() -> new LikeParameterBinding(0, Type.CONTAINING)); - } - - @Test - void setsUpInstanceForName() { - - LikeParameterBinding binding = new LikeParameterBinding("foo", "foo", Type.CONTAINING); - - assertThat(binding.hasName("foo")).isTrue(); - assertThat(binding.hasName("bar")).isFalse(); - assertThat(binding.hasName(null)).isFalse(); - assertThat(binding.hasPosition(0)).isFalse(); - assertThat(binding.getType()).isEqualTo(Type.CONTAINING); - } - - @Test - void setsUpInstanceForIndex() { - - LikeParameterBinding binding = new LikeParameterBinding(1, Type.CONTAINING); - - assertThat(binding.hasName("foo")).isFalse(); - assertThat(binding.hasName(null)).isFalse(); - assertThat(binding.hasPosition(0)).isFalse(); - assertThat(binding.hasPosition(1)).isTrue(); - assertThat(binding.getType()).isEqualTo(Type.CONTAINING); + assertThatIllegalArgumentException().isThrownBy( + () -> new LikeParameterBinding(BindingIdentifier.of(0), ParameterOrigin.ofExpression(""), Type.CONTAINING)); } @Test @@ -92,6 +76,7 @@ class LikeBindingUnitTests { assertAugmentedValue(Type.ENDING_WITH, "%value"); assertAugmentedValue(Type.STARTING_WITH, "value%"); - assertThat(new LikeParameterBinding(1, Type.CONTAINING).prepare(null)).isNull(); + assertThat(new LikeParameterBinding(BindingIdentifier.of(1), ParameterOrigin.ofParameter(null, 1), Type.CONTAINING) + .prepare(null)).isNull(); } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java index b10df30d3..8826ea60d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java @@ -25,9 +25,8 @@ import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; - import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; -import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding; +import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin; /** * Unit tests for {@link QueryParameterSetterFactory}. @@ -60,6 +59,8 @@ class QueryParameterSetterFactoryUnitTests { @Test // DATAJPA-1058 void exceptionWhenQueryContainNamedParametersAndMethodParametersAreNotNamed() { + when(binding.getOrigin()).thenReturn(ParameterOrigin.ofParameter("NamedParameter", 1)); + assertThatExceptionOfType(IllegalStateException.class) // .isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith :NamedParameter", false))) // .withMessageContaining("Java 8") // @@ -76,6 +77,7 @@ class QueryParameterSetterFactoryUnitTests { // one argument present in the method signature when(binding.getRequiredPosition()).thenReturn(1); + when(binding.getOrigin()).thenReturn(ParameterOrigin.ofParameter(null, 1)); assertThatExceptionOfType(IllegalArgumentException.class) // .isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith :NamedParameter", false))) // @@ -90,6 +92,7 @@ class QueryParameterSetterFactoryUnitTests { // one argument present in the method signature when(binding.getRequiredPosition()).thenReturn(1); + when(binding.getOrigin()).thenReturn(ParameterOrigin.ofParameter(null, 1)); assertThatExceptionOfType(IllegalArgumentException.class) // .isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith ?1", false))) // diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java index 101574af6..dd523fc74 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java @@ -23,9 +23,9 @@ import java.util.List; import org.assertj.core.api.Assertions; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; -import org.springframework.data.jpa.repository.query.StringQuery.InParameterBinding; -import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding; -import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding; +import org.springframework.data.jpa.repository.query.ParameterBinding.Expression; +import org.springframework.data.jpa.repository.query.ParameterBinding.InParameterBinding; +import org.springframework.data.jpa.repository.query.ParameterBinding.LikeParameterBinding; import org.springframework.data.repository.query.parser.Part.Type; /** @@ -58,7 +58,7 @@ class StringQueryUnitTests { LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0); assertThat(binding.getType()).isEqualTo(Type.LIKE); - assertThat(binding.hasName("firstname")).isTrue(); + assertThat(binding.getName()).isEqualTo("firstname"); } @Test // DATAJPA-292 @@ -76,12 +76,12 @@ class StringQueryUnitTests { LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0); assertThat(binding).isNotNull(); - assertThat(binding.hasPosition(1)).isTrue(); + assertThat(binding.getRequiredPosition()).isEqualTo(1); assertThat(binding.getType()).isEqualTo(Type.CONTAINING); binding = (LikeParameterBinding) bindings.get(1); assertThat(binding).isNotNull(); - assertThat(binding.hasPosition(2)).isTrue(); + assertThat(binding.getRequiredPosition()).isEqualTo(2); assertThat(binding.getType()).isEqualTo(Type.ENDING_WITH); } @@ -98,7 +98,7 @@ class StringQueryUnitTests { LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0); assertThat(binding).isNotNull(); - assertThat(binding.hasName("firstname")).isTrue(); + assertThat(binding.getRequiredName()).isEqualTo("firstname"); assertThat(binding.getType()).isEqualTo(Type.ENDING_WITH); } @@ -117,12 +117,12 @@ class StringQueryUnitTests { LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0); assertThat(binding).isNotNull(); - assertThat(binding.hasName("firstname")).isTrue(); + assertThat(binding.getName()).isEqualTo("firstname"); assertThat(binding.getType()).isEqualTo(Type.ENDING_WITH); binding = (LikeParameterBinding) bindings.get(1); assertThat(binding).isNotNull(); - assertThat(binding.hasName("firstname_1")).isTrue(); + assertThat(binding.getName()).isEqualTo("firstname_1"); assertThat(binding.getType()).isEqualTo(Type.STARTING_WITH); } @@ -142,12 +142,12 @@ class StringQueryUnitTests { LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0); assertThat(binding).isNotNull(); - assertThat(binding.hasName("firstname")).isTrue(); + assertThat(binding.getName()).isEqualTo("firstname"); assertThat(binding.getType()).isEqualTo(Type.ENDING_WITH); binding = (LikeParameterBinding) bindings.get(1); assertThat(binding).isNotNull(); - assertThat(binding.hasName("firstname_1")).isTrue(); + assertThat(binding.getName()).isEqualTo("firstname_1"); assertThat(binding.getType()).isEqualTo(Type.CONTAINING); } @@ -359,11 +359,9 @@ class StringQueryUnitTests { StringQuery query = new StringQuery("select a from A a where a.b in ?#{#bs} and a.c in ?#{#cs}", true); String queryString = query.getQueryString(); - softly.assertThat(queryString).isEqualTo("select a from A a where a.b in ?1 and a.c in ?2"); - softly.assertThat(query.getParameterBindings().get(0).getExpression()).isEqualTo("#bs"); - softly.assertThat(query.getParameterBindings().get(1).getExpression()).isEqualTo("#cs"); - - softly.assertAll(); + assertThat(queryString).isEqualTo("select a from A a where a.b in ?1 and a.c in ?2"); + assertThat(((Expression) query.getParameterBindings().get(0).getOrigin()).expression()).isEqualTo("#bs"); + assertThat(((Expression) query.getParameterBindings().get(1).getOrigin()).expression()).isEqualTo("#cs"); } @Test // DATAJPA-864 @@ -398,15 +396,13 @@ class StringQueryUnitTests { StringQuery query = new StringQuery("select a from A a where a.first = :#{#exp} or a.second = :#{#exp}", true); List bindings = query.getParameterBindings(); - softly.assertThat(bindings).isNotEmpty(); + assertThat(bindings).isNotEmpty(); for (ParameterBinding binding : bindings) { - softly.assertThat(binding.getName()).isNotNull(); - softly.assertThat(query.getQueryString()).contains(binding.getName()); - softly.assertThat(binding.getExpression()).isEqualTo("#exp"); + assertThat(binding.getName()).isNotNull(); + assertThat(query.getQueryString()).contains(binding.getName()); + assertThat(((Expression) binding.getOrigin()).expression()).isEqualTo("#exp"); } - - softly.assertAll(); } @Test // DATAJPA-1235 @@ -646,16 +642,16 @@ class StringQueryUnitTests { private void assertPositionalBinding(Class bindingType, Integer position, ParameterBinding expectedBinding) { - softly.assertThat(bindingType.isInstance(expectedBinding)).isTrue(); - softly.assertThat(expectedBinding).isNotNull(); - softly.assertThat(expectedBinding.hasPosition(position)).isTrue(); + assertThat(bindingType.isInstance(expectedBinding)).isTrue(); + assertThat(expectedBinding).isNotNull(); + assertThat(expectedBinding.getPosition()).isEqualTo(position); } private void assertNamedBinding(Class bindingType, String parameterName, ParameterBinding expectedBinding) { - softly.assertThat(bindingType.isInstance(expectedBinding)).isTrue(); - softly.assertThat(expectedBinding).isNotNull(); - softly.assertThat(expectedBinding.hasName(parameterName)).isTrue(); + assertThat(bindingType.isInstance(expectedBinding)).isTrue(); + assertThat(expectedBinding).isNotNull(); + assertThat(expectedBinding.getName()).isEqualTo(parameterName); } }