@@ -54,6 +54,7 @@ class StringQuery implements DeclaredQuery {
54
54
private final @ Nullable String alias ;
55
55
private final boolean hasConstructorExpression ;
56
56
private final boolean containsPageableInSpel ;
57
+ private final boolean usesJdbcStyleParameters ;
57
58
58
59
/**
59
60
* Creates a new {@link StringQuery} from the given JPQL query.
@@ -67,9 +68,12 @@ class StringQuery implements DeclaredQuery {
67
68
68
69
this .bindings = new ArrayList <>();
69
70
this .containsPageableInSpel = query .contains ("#pageable" );
71
+
72
+ Metadata queryMeta = new Metadata ();
70
73
this .query = ParameterBindingParser .INSTANCE .parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery (query ,
71
- this .bindings );
74
+ this .bindings , queryMeta );
72
75
76
+ this .usesJdbcStyleParameters = queryMeta .usesJdbcStyleParameters ;
73
77
this .alias = QueryUtils .detectAlias (query );
74
78
this .hasConstructorExpression = QueryUtils .hasConstructorExpression (query );
75
79
}
@@ -112,7 +116,7 @@ public DeclaredQuery deriveCountQuery(@Nullable String countQuery, @Nullable Str
112
116
*/
113
117
@ Override
114
118
public boolean usesJdbcStyleParameters () {
115
- return false ;
119
+ return usesJdbcStyleParameters ;
116
120
}
117
121
118
122
/*
@@ -180,7 +184,11 @@ enum ParameterBindingParser {
180
184
INSTANCE ;
181
185
182
186
private static final String EXPRESSION_PARAMETER_PREFIX = "__$synthetic$__" ;
183
- private static final Pattern PARAMETER_BINDING_BY_INDEX = Pattern .compile ("\\ ?(\\ d+)" );
187
+ public static final String POSITIONAL_OR_INDEXED_PARAMETER = "\\ ?(\\ d*+(?![#\\ w]))" ;
188
+ // .....................................................................^ not followed by a hash or a letter.
189
+ // .................................................................^ zero or more digits.
190
+ // .............................................................^ start with a question mark.
191
+ private static final Pattern PARAMETER_BINDING_BY_INDEX = Pattern .compile (POSITIONAL_OR_INDEXED_PARAMETER );
184
192
private static final Pattern PARAMETER_BINDING_PATTERN ;
185
193
private static final String MESSAGE = "Already found parameter binding with same index / parameter name but differing binding type! "
186
194
+ "Already have: %s, found %s! If you bind a parameter multiple times make sure they use the same binding." ;
@@ -205,7 +213,7 @@ enum ParameterBindingParser {
205
213
builder .append ("(?: )?" ); // some whitespace
206
214
builder .append ("\\ (?" ); // optional braces around parameters
207
215
builder .append ("(" );
208
- builder .append ("%?(\\ ?( \\ d+) )%?" ); // position parameter and parameter index
216
+ builder .append ("%?(" + POSITIONAL_OR_INDEXED_PARAMETER + " )%?" ); // position parameter and parameter index
209
217
builder .append ("|" ); // or
210
218
211
219
// named parameter and the parameter name
@@ -222,13 +230,29 @@ enum ParameterBindingParser {
222
230
* the cleaned up query.
223
231
*/
224
232
private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery (String query ,
225
- List <ParameterBinding > bindings ) {
233
+ List <ParameterBinding > bindings , Metadata queryMeta ) {
234
+
235
+ int greatestParameterIndex = tryFindGreatestParameterIndexIn (query );
226
236
227
- SpelExtractor spelExtractor = createSpelExtractor (query );
237
+ boolean parametersShouldBeAccessedByIndex = greatestParameterIndex != -1 ;
238
+
239
+ /*
240
+ * Prefer indexed access over named parameters if only SpEL Expression parameters are present.
241
+ */
242
+ if (!parametersShouldBeAccessedByIndex && query .contains ("?#{" )) {
243
+ parametersShouldBeAccessedByIndex = true ;
244
+ greatestParameterIndex = 0 ;
245
+ }
246
+
247
+ SpelExtractor spelExtractor = createSpelExtractor (query , parametersShouldBeAccessedByIndex ,
248
+ greatestParameterIndex );
228
249
229
250
String resultingQuery = spelExtractor .getQueryString ();
230
251
Matcher matcher = PARAMETER_BINDING_PATTERN .matcher (resultingQuery );
231
252
253
+ int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0 ;
254
+
255
+ boolean usesJpaStyleParameters = false ;
232
256
while (matcher .find ()) {
233
257
234
258
if (spelExtractor .isQuoted (matcher .start ())) {
@@ -237,12 +261,26 @@ private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(St
237
261
238
262
String parameterIndexString = matcher .group (INDEXED_PARAMETER_GROUP );
239
263
String parameterName = parameterIndexString != null ? null : matcher .group (NAMED_PARAMETER_GROUP );
240
- Integer parameterIndex = parameterIndexString == null ? null : Integer .valueOf (parameterIndexString );
264
+ Integer parameterIndex = getParameterIndex (parameterIndexString );
265
+
241
266
String typeSource = matcher .group (COMPARISION_TYPE_GROUP );
242
267
String expression = spelExtractor .getParameter (parameterName == null ? parameterIndexString : parameterName );
243
268
String replacement = null ;
244
269
245
- Assert .isTrue (parameterIndex != null || parameterName != null , "We need either a name or an index." );
270
+ Assert .isTrue (parameterIndexString != null || parameterName != null , "We need either a name or an index." );
271
+
272
+ expressionParameterIndex ++;
273
+ if ("" .equals (parameterIndexString )) {
274
+
275
+ queryMeta .usesJdbcStyleParameters = true ;
276
+ parameterIndex = expressionParameterIndex ;
277
+ } else {
278
+ usesJpaStyleParameters = true ;
279
+ }
280
+
281
+ if (usesJpaStyleParameters && queryMeta .usesJdbcStyleParameters ) {
282
+ throw new IllegalArgumentException ("Mixing of ? parameters and other forms like ?1 is not supported" );
283
+ }
246
284
247
285
switch (ParameterBindingType .of (typeSource )) {
248
286
@@ -287,19 +325,8 @@ private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(St
287
325
return resultingQuery ;
288
326
}
289
327
290
- private SpelExtractor createSpelExtractor (String queryWithSpel ) {
291
-
292
- int greatestParameterIndex = tryFindGreatestParameterIndexIn (queryWithSpel );
293
-
294
- boolean parametersShouldBeAccessedByIndex = greatestParameterIndex != -1 ;
295
-
296
- /*
297
- * Prefer indexed access over named parameters if only SpEL Expression parameters are present.
298
- */
299
- if (!parametersShouldBeAccessedByIndex && queryWithSpel .contains ("?#{" )) {
300
- parametersShouldBeAccessedByIndex = true ;
301
- greatestParameterIndex = 0 ;
302
- }
328
+ private SpelExtractor createSpelExtractor (String queryWithSpel , boolean parametersShouldBeAccessedByIndex ,
329
+ int greatestParameterIndex ) {
303
330
304
331
/*
305
332
* If parameters need to be bound by index, we bind the synthetic expression parameters starting from position of the greatest discovered index parameter in order to
@@ -328,14 +355,27 @@ private String replaceFirst(String text, String substring, String replacement) {
328
355
return text .substring (0 , index ) + replacement + text .substring (index + substring .length ());
329
356
}
330
357
358
+ @ Nullable
359
+ private Integer getParameterIndex (@ Nullable String parameterIndexString ) {
360
+
361
+ if (parameterIndexString == null || parameterIndexString .isEmpty ()) {
362
+ return null ;
363
+ }
364
+ return Integer .valueOf (parameterIndexString );
365
+ }
366
+
331
367
private int tryFindGreatestParameterIndexIn (String query ) {
332
368
333
369
Matcher parameterIndexMatcher = PARAMETER_BINDING_BY_INDEX .matcher (query );
334
370
335
371
int greatestParameterIndex = -1 ;
336
372
while (parameterIndexMatcher .find ()) {
373
+
337
374
String parameterIndexString = parameterIndexMatcher .group (1 );
338
- greatestParameterIndex = Math .max (greatestParameterIndex , Integer .parseInt (parameterIndexString ));
375
+ Integer parameterIndex = getParameterIndex (parameterIndexString );
376
+ if (parameterIndex != null ) {
377
+ greatestParameterIndex = Math .max (greatestParameterIndex , parameterIndex );
378
+ }
339
379
}
340
380
341
381
return greatestParameterIndex ;
@@ -790,4 +830,7 @@ private static Type getLikeTypeFrom(String expression) {
790
830
}
791
831
}
792
832
833
+ static class Metadata {
834
+ private boolean usesJdbcStyleParameters = false ;
835
+ }
793
836
}
0 commit comments