20
20
import static org .springframework .util .ObjectUtils .nullSafeHashCode ;
21
21
22
22
import java .lang .reflect .Array ;
23
- import java .util .ArrayList ;
24
- import java .util .Arrays ;
25
- import java .util .Collection ;
26
- import java .util .List ;
23
+ import java .util .*;
27
24
import java .util .function .BiFunction ;
28
25
import java .util .regex .Matcher ;
29
26
import java .util .regex .Pattern ;
51
48
* @author Diego Krupitza
52
49
* @author Greg Turnquist
53
50
* @author Yuriy Tsarkov
51
+ * @author Klajdi Paja
54
52
*/
55
53
class StringQuery implements DeclaredQuery {
56
54
55
+ public static final String LIKE_BINDING_RENAMING_PREFIX = "_like_binding_prefix" ;
57
56
private final String query ;
58
57
private final List <ParameterBinding > bindings ;
59
58
private final @ Nullable String alias ;
@@ -235,6 +234,8 @@ private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(St
235
234
int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0 ;
236
235
237
236
boolean usesJpaStyleParameters = false ;
237
+ HashMap <Object ,Integer > occurrencesMap =buildOccurrencesMap (matcher ,resultingQuery );
238
+
238
239
while (matcher .find ()) {
239
240
240
241
if (spelExtractor .isQuoted (matcher .start ())) {
@@ -269,14 +270,23 @@ private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(St
269
270
case LIKE :
270
271
271
272
Type likeType = LikeParameterBinding .getLikeTypeFrom (matcher .group (2 ));
272
- replacement = matcher .group (3 );
273
+
274
+ // Convert the query defined by the user by replacing repeated parameters
275
+ // in a Like compare operator with a dynamic unique name.
276
+ // example: Select u from user u where u.name=:q or name like %:q%
277
+ // should be converted to: Select u from user u where u.name=:q or name like :q_prefix
278
+ // This would make sure that the value passed to q parameter is set correctly by the JPA Provider
273
279
274
280
if (parameterIndex != null ) {
275
- checkAndRegister (new LikeParameterBinding (parameterIndex , likeType , expression ), bindings );
281
+ int updatedPosition = generateNewPosition (bindings , resultingQuery , occurrencesMap , parameterIndex , likeType );
282
+ LikeParameterBinding binding = new LikeParameterBinding (parameterIndex , updatedPosition , likeType , expression );
283
+ checkAndRegister (binding , bindings );
284
+ replacement = "?" + updatedPosition ;
276
285
} else {
277
- checkAndRegister (new LikeParameterBinding (parameterName , likeType , expression ), bindings );
286
+ String updatedName = generateUpdatedName (bindings , occurrencesMap , parameterName , likeType );
287
+ checkAndRegister (new LikeParameterBinding (parameterName , updatedName , likeType , expression ), bindings );
278
288
279
- replacement = ":" + parameterName ;
289
+ replacement = ":" + updatedName ;
280
290
}
281
291
282
292
break ;
@@ -308,6 +318,64 @@ private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(St
308
318
return resultingQuery ;
309
319
}
310
320
321
+ private static String generateUpdatedName (List <ParameterBinding > bindings , HashMap <Object , Integer > occurrencesMap ,
322
+ String parameterName , Type likeType ) {
323
+ Integer occurrences = occurrencesMap .get (parameterName );
324
+ boolean parameterIsRepeated = occurrences != null && occurrences > 1 ;
325
+ String updatedName = parameterName ;
326
+ if (parameterIsRepeated ) {
327
+ Optional <LikeParameterBinding > existingLikeBinding = findLikeBindingByNameOrIndex (parameterName , null , likeType ,
328
+ bindings );
329
+ updatedName = existingLikeBinding .map (LikeParameterBinding ::getUpdatedName )
330
+ .orElseGet (() -> parameterName + LIKE_BINDING_RENAMING_PREFIX );
331
+ }
332
+ return updatedName ;
333
+ }
334
+
335
+ private static int generateNewPosition (List <ParameterBinding > bindings , String resultingQuery ,
336
+ HashMap <Object , Integer > occurrencesMap , Integer parameterIndex , Type likeType ) {
337
+ int updatedPosition ;
338
+ Integer count = occurrencesMap .get (parameterIndex );
339
+ if (count != null && count > 1 ) {
340
+ int maxCurrentIndex = tryFindGreatestParameterIndexIn (resultingQuery );
341
+ updatedPosition = maxCurrentIndex + 1 ;
342
+ } else
343
+ updatedPosition = parameterIndex ;
344
+
345
+ Optional <LikeParameterBinding > existingLikeBinding = findLikeBindingByNameOrIndex (null , parameterIndex , likeType ,
346
+ bindings );
347
+ if (existingLikeBinding .isPresent ()) {
348
+ updatedPosition = existingLikeBinding .get ().getUpdatedPosition ();
349
+ }
350
+ return updatedPosition ;
351
+ }
352
+
353
+ private HashMap <Object , Integer > buildOccurrencesMap (Matcher m , String resultingQuery ) {
354
+ Matcher matcher = PARAMETER_BINDING_PATTERN .matcher (resultingQuery );
355
+ HashMap <Object , Integer > map = new HashMap <>();
356
+ while (matcher .find ()) {
357
+ String parameterIndexString = matcher .group (INDEXED_PARAMETER_GROUP );
358
+ boolean indexBasedAccess =parameterIndexString != null ;
359
+ String parameterName = indexBasedAccess ? null : matcher .group (NAMED_PARAMETER_GROUP );
360
+ Integer parameterIndex = getParameterIndex (parameterIndexString );
361
+ Object param =indexBasedAccess ?parameterIndex :parameterName ;
362
+ map .compute (param , (key , value ) -> value == null ? 1 : value +1 );
363
+ }
364
+ return map ;
365
+ }
366
+
367
+ private static Optional <LikeParameterBinding > findLikeBindingByNameOrIndex (@ Nullable String name ,@ Nullable Integer index ,
368
+ Type type , List <ParameterBinding > bindings ) {
369
+
370
+ return bindings .stream ()//
371
+ .filter (it -> it instanceof LikeParameterBinding )//
372
+ .map (it -> (LikeParameterBinding ) it )//
373
+ .filter (it -> it .getType ().equals (type ))//
374
+ .filter (it ->name ==null ||name .equals (it .getRequiredName ()))//
375
+ .filter (it ->index ==null || index .equals (it .getPosition ()))//
376
+ .findFirst ();
377
+ }
378
+
311
379
private static SpelExtractor createSpelExtractor (String queryWithSpel , boolean parametersShouldBeAccessedByIndex ,
312
380
int greatestParameterIndex ) {
313
381
@@ -637,6 +705,8 @@ static class LikeParameterBinding extends ParameterBinding {
637
705
Type .ENDING_WITH , Type .LIKE );
638
706
639
707
private final Type type ;
708
+ private String updatedName ;
709
+ private Integer updatedPosition ;
640
710
641
711
/**
642
712
* Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type}.
@@ -669,6 +739,23 @@ static class LikeParameterBinding extends ParameterBinding {
669
739
this .type = type ;
670
740
}
671
741
742
+ /**
743
+ * Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type} and parameter
744
+ * binding input.
745
+ *
746
+ * @param name must not be {@literal null} or empty.
747
+ * @param updatedName the updated name of the parameter in the query. A new name should be created for the Like parameter
748
+ * if a parameter shows up more than once in a query, it can be used later to set the value properly.
749
+ * @param type must not be {@literal null}.
750
+ * @param expression may be {@literal null}.
751
+ */
752
+ LikeParameterBinding (String name , String updatedName , Type type , @ Nullable String expression ) {
753
+ this (name , type , expression );
754
+ Assert .hasText (updatedName , "Updated Name must not be null or empty" );
755
+ this .updatedName = updatedName ;
756
+
757
+ }
758
+
672
759
/**
673
760
* Creates a new {@link LikeParameterBinding} for the parameter with the given position and {@link Type}.
674
761
*
@@ -699,6 +786,23 @@ static class LikeParameterBinding extends ParameterBinding {
699
786
this .type = type ;
700
787
}
701
788
789
+ /**
790
+ * Creates a new {@link LikeParameterBinding} for the parameter with the given position, the updated position and {@link Type}.
791
+ *
792
+ * @param position position of the parameter in the query.
793
+ * @param updatedPosition the updated position of the parameter in the query. A new position should be created for the Like parameter
794
+ * if a parameter shows up more than once in a query that can be used later to set the value properly.
795
+ * @param type must not be {@literal null}.
796
+ * @param expression may be {@literal null}.
797
+ */
798
+ LikeParameterBinding (int position , int updatedPosition , Type type , @ Nullable String expression ) {
799
+ this (position , type , expression );
800
+
801
+ Assert .isTrue (updatedPosition > 0 , "UpdatedPosition must be greater than zero" );
802
+ this .updatedPosition = updatedPosition ;
803
+
804
+ }
805
+
702
806
/**
703
807
* Returns the {@link Type} of the binding.
704
808
*
@@ -708,6 +812,32 @@ public Type getType() {
708
812
return type ;
709
813
}
710
814
815
+ /**
816
+ * Returns the Updated Position of the binding in the query.
817
+ *
818
+ * @return the updated position
819
+ */
820
+ public Integer getUpdatedPosition () {
821
+ return updatedPosition ;
822
+ }
823
+
824
+ /**
825
+ * Returns the Updated Name of the binding in the query.
826
+ *
827
+ * @return the updated name
828
+ */
829
+ public String getUpdatedName () {
830
+ return updatedName ;
831
+ }
832
+
833
+ /**
834
+ * Check if the bindings updated name is equal to the provided name.
835
+ *
836
+ */
837
+ boolean hasUpdatedName (@ Nullable String name ) {
838
+ return this .updatedPosition == null && this .updatedName != null && this .updatedName .equals (name );
839
+ }
840
+
711
841
/**
712
842
* Prepares the given raw keyword according to the like type.
713
843
*/
@@ -742,7 +872,8 @@ public boolean equals(Object obj) {
742
872
743
873
LikeParameterBinding that = (LikeParameterBinding ) obj ;
744
874
745
- return super .equals (obj ) && this .type .equals (that .type );
875
+ return super .equals (obj ) && this .type .equals (that .type ) && Objects .equals (this .updatedName ,
876
+ that .updatedName ) && Objects .equals (this .updatedPosition , that .updatedPosition );
746
877
}
747
878
748
879
@ Override
@@ -751,13 +882,16 @@ public int hashCode() {
751
882
int result = super .hashCode ();
752
883
753
884
result += nullSafeHashCode (this .type );
885
+ result += nullSafeHashCode (this .updatedName );
886
+ result += nullSafeHashCode (this .updatedPosition );
754
887
755
888
return result ;
756
889
}
757
890
758
891
@ Override
759
892
public String toString () {
760
- return String .format ("LikeBinding [name: %s, position: %d, type: %s]" , getName (), getPosition (), type );
893
+ return String .format ("LikeBinding [name: %s, position: %d, type: %s, updatedName: %s, updatedPosition: %d]" ,
894
+ getName (), getPosition (), type , updatedPosition , updatedPosition );
761
895
}
762
896
763
897
/**
0 commit comments