Skip to content

Commit a55480f

Browse files
mp911degregturn
authored andcommitted
Ensure correct bindings for parameter reuse.
We now verify all bindings to ensure that a like-parameter doesn't mix up with plain bindings (e.g. firstname = %:myparam or lastname = :myparam). We also create unique synthetic parameters for positional bindings. Also, fix Regex to properly detect named, anonymous and positional bind markers. See #3041
1 parent c223481 commit a55480f

File tree

7 files changed

+392
-144
lines changed

7 files changed

+392
-144
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java

+66-49
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.List;
2525

2626
import org.springframework.data.jpa.provider.PersistenceProvider;
27-
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
2827
import org.springframework.data.repository.query.parser.Part.Type;
2928
import org.springframework.lang.Nullable;
3029
import org.springframework.util.Assert;
@@ -151,8 +150,8 @@ public Object prepare(@Nullable Object valueToBind) {
151150
/**
152151
* Check whether the {@code other} binding uses the same bind target.
153152
*
154-
* @param other
155-
* @return
153+
* @param other must not be {@literal null}.
154+
* @return {@code true} if the other binding uses the same parameter to bind to as this one.
156155
*/
157156
public boolean bindsTo(ParameterBinding other) {
158157

@@ -171,6 +170,17 @@ public boolean bindsTo(ParameterBinding other) {
171170
return false;
172171
}
173172

173+
/**
174+
* Check whether this binding can be bound as the {@code other} binding by checking its type and origin. Subclasses
175+
* may override this method to include other properties for the compatibility check.
176+
*
177+
* @param other
178+
* @return {@code true} if the other binding is compatible with this one.
179+
*/
180+
public boolean isCompatibleWith(ParameterBinding other) {
181+
return other.getClass() == getClass() && other.getOrigin().equals(getOrigin());
182+
}
183+
174184
/**
175185
* Represents a {@link ParameterBinding} in a JPQL query augmented with instructions of how to apply a parameter as an
176186
* {@code IN} parameter.
@@ -294,6 +304,16 @@ public String toString() {
294304
getType());
295305
}
296306

307+
@Override
308+
public boolean isCompatibleWith(ParameterBinding binding) {
309+
310+
if (super.isCompatibleWith(binding) && binding instanceof LikeParameterBinding other) {
311+
return getType() == other.getType();
312+
}
313+
314+
return false;
315+
}
316+
297317
/**
298318
* Extracts the like {@link Type} from the given JPA like expression.
299319
*
@@ -319,52 +339,12 @@ static Type getLikeTypeFrom(String expression) {
319339
}
320340
}
321341

322-
static class ParameterImpl<T> implements jakarta.persistence.Parameter<T> {
323-
324-
private final BindingIdentifier identifier;
325-
private final Class<T> parameterType;
326-
327-
/**
328-
* Creates a new {@link ParameterImpl} for the given {@link JpaParameter} and {@link ParameterBinding}.
329-
*
330-
* @param parameter can be {@literal null}.
331-
* @param binding must not be {@literal null}.
332-
* @return a {@link jakarta.persistence.Parameter} object based on the information from the arguments.
333-
*/
334-
static jakarta.persistence.Parameter<?> of(@Nullable JpaParameter parameter, ParameterBinding binding) {
335-
336-
Class<?> type = parameter == null ? Object.class : parameter.getType();
337-
338-
return new ParameterImpl<>(binding.getIdentifier(), type);
339-
}
340-
341-
public ParameterImpl(BindingIdentifier identifier, Class<T> parameterType) {
342-
this.identifier = identifier;
343-
this.parameterType = parameterType;
344-
}
345-
346-
@Nullable
347-
@Override
348-
public String getName() {
349-
return identifier.hasName() ? identifier.getName() : null;
350-
}
351-
352-
@Nullable
353-
@Override
354-
public Integer getPosition() {
355-
return identifier.hasPosition() ? identifier.getPosition() : null;
356-
}
357-
358-
@Override
359-
public Class<T> getParameterType() {
360-
return parameterType;
361-
}
362-
363-
}
364-
365342
/**
366343
* Identifies a binding parameter by name, position or both. Used to bind parameters to a query or to describe a
367344
* {@link MethodInvocationArgument} origin.
345+
*
346+
* @author Mark Paluch
347+
* @since 3.1.2
368348
*/
369349
sealed interface BindingIdentifier permits Named,Indexed,NamedAndIndexed {
370350

@@ -453,6 +433,11 @@ public boolean hasName() {
453433
public String getName() {
454434
return name();
455435
}
436+
437+
@Override
438+
public String toString() {
439+
return name();
440+
}
456441
}
457442

458443
private record Indexed(int position) implements BindingIdentifier {
@@ -466,6 +451,11 @@ public boolean hasPosition() {
466451
public int getPosition() {
467452
return position();
468453
}
454+
455+
@Override
456+
public String toString() {
457+
return "[" + position() + "]";
458+
}
469459
}
470460

471461
private record NamedAndIndexed(String name, int position) implements BindingIdentifier {
@@ -489,10 +479,18 @@ public boolean hasPosition() {
489479
public int getPosition() {
490480
return position();
491481
}
482+
483+
@Override
484+
public String toString() {
485+
return "[" + name() + ", " + position() + "]";
486+
}
492487
}
493488

494489
/**
495490
* Value type hierarchy to describe where a binding parameter comes from, either method call or an expression.
491+
*
492+
* @author Mark Paluch
493+
* @since 3.1.2
496494
*/
497495
sealed interface ParameterOrigin permits Expression,MethodInvocationArgument {
498496

@@ -508,11 +506,11 @@ static Expression ofExpression(String expression) {
508506

509507
/**
510508
* Creates a {@link MethodInvocationArgument} object for {@code name} and {@code position}. Either the name or the
511-
* position must be given,
509+
* position must be given.
512510
*
513511
* @param name the parameter name from the method invocation, can be {@literal null}.
514512
* @param position the parameter position (1-based) from the method invocation, can be {@literal null}.
515-
* @return {@link MethodInvocationArgument} object for {@code name} and {@code position}
513+
* @return {@link MethodInvocationArgument} object for {@code name} and {@code position}.
516514
*/
517515
static MethodInvocationArgument ofParameter(@Nullable String name, @Nullable Integer position) {
518516

@@ -528,26 +526,43 @@ static MethodInvocationArgument ofParameter(@Nullable String name, @Nullable Int
528526
return ofParameter(identifier);
529527
}
530528

529+
/**
530+
* Creates a {@link MethodInvocationArgument} object for {@code position}.
531+
*
532+
* @param position the parameter position (1-based) from the method invocation.
533+
* @return {@link MethodInvocationArgument} object for {@code position}.
534+
*/
535+
static MethodInvocationArgument ofParameter(int position) {
536+
return ofParameter(BindingIdentifier.of(position));
537+
}
538+
531539
/**
532540
* Creates a {@link MethodInvocationArgument} using {@link BindingIdentifier}.
533541
*
534542
* @param identifier must not be {@literal null}.
535543
* @return {@link MethodInvocationArgument} for {@link BindingIdentifier}.
536544
*/
537545
static MethodInvocationArgument ofParameter(BindingIdentifier identifier) {
538-
539546
return new MethodInvocationArgument(identifier);
540547
}
541548

549+
/**
550+
* @return {@code true} if the origin is a method argument reference.
551+
*/
542552
boolean isMethodArgument();
543553

554+
/**
555+
* @return {@code true} if the origin is an expression.
556+
*/
544557
boolean isExpression();
545558
}
546559

547560
/**
548561
* Value object capturing the expression of which a binding parameter originates.
549562
*
550563
* @param expression
564+
* @author Mark Paluch
565+
* @since 3.1.2
551566
*/
552567
public record Expression(String expression) implements ParameterOrigin {
553568

@@ -566,6 +581,8 @@ public boolean isExpression() {
566581
* Value object capturing the method invocation parameter reference.
567582
*
568583
* @param identifier
584+
* @author Mark Paluch
585+
* @since 3.1.2
569586
*/
570587
public record MethodInvocationArgument(BindingIdentifier identifier) implements ParameterOrigin {
571588

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java

+43-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
2525
import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier;
2626
import org.springframework.data.jpa.repository.query.ParameterBinding.MethodInvocationArgument;
27-
import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterImpl;
2827
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
2928
import org.springframework.data.jpa.repository.query.QueryParameterSetter.NamedOrIndexedQueryParameterSetter;
3029
import org.springframework.data.repository.query.Parameter;
@@ -323,4 +322,47 @@ private Object getAndPrepare(JpaParameter parameter, ParameterMetadata<?> metada
323322
}
324323
}
325324

325+
static class ParameterImpl<T> implements jakarta.persistence.Parameter<T> {
326+
327+
private final BindingIdentifier identifier;
328+
private final Class<T> parameterType;
329+
330+
/**
331+
* Creates a new {@link ParameterImpl} for the given {@link JpaParameter} and {@link ParameterBinding}.
332+
*
333+
* @param parameter can be {@literal null}.
334+
* @param binding must not be {@literal null}.
335+
* @return a {@link jakarta.persistence.Parameter} object based on the information from the arguments.
336+
*/
337+
static jakarta.persistence.Parameter<?> of(@Nullable JpaParameter parameter, ParameterBinding binding) {
338+
339+
Class<?> type = parameter == null ? Object.class : parameter.getType();
340+
341+
return new ParameterImpl<>(binding.getIdentifier(), type);
342+
}
343+
344+
public ParameterImpl(BindingIdentifier identifier, Class<T> parameterType) {
345+
this.identifier = identifier;
346+
this.parameterType = parameterType;
347+
}
348+
349+
@Nullable
350+
@Override
351+
public String getName() {
352+
return identifier.hasName() ? identifier.getName() : null;
353+
}
354+
355+
@Nullable
356+
@Override
357+
public Integer getPosition() {
358+
return identifier.hasPosition() ? identifier.getPosition() : null;
359+
}
360+
361+
@Override
362+
public Class<T> getParameterType() {
363+
return parameterType;
364+
}
365+
366+
}
367+
326368
}

0 commit comments

Comments
 (0)