20
20
import java .util .ArrayList ;
21
21
import java .util .Collection ;
22
22
import java .util .List ;
23
+ import java .util .Set ;
24
+ import java .util .TreeSet ;
23
25
import java .util .function .BiFunction ;
24
26
import java .util .function .Consumer ;
25
27
import java .util .function .Function ;
@@ -191,6 +193,80 @@ public boolean isNativeQuery() {
191
193
return isNative ;
192
194
}
193
195
196
+ /**
197
+ * Value object to track and allocate used parameter index labels in a query.
198
+ */
199
+ static class IndexedParameterLabels {
200
+
201
+ private final TreeSet <Integer > usedLabels ;
202
+ private final boolean sequential ;
203
+
204
+ public IndexedParameterLabels (Set <Integer > usedLabels ) {
205
+
206
+ this .usedLabels = usedLabels instanceof TreeSet <Integer > ts ? ts : new TreeSet <Integer >(usedLabels );
207
+ this .sequential = isSequential (usedLabels );
208
+ }
209
+
210
+ private static boolean isSequential (Set <Integer > usedLabels ) {
211
+
212
+ for (int i = 0 ; i < usedLabels .size (); i ++) {
213
+
214
+ if (usedLabels .contains (i + 1 )) {
215
+ continue ;
216
+ }
217
+
218
+ return false ;
219
+ }
220
+
221
+ return true ;
222
+ }
223
+
224
+ /**
225
+ * Allocate the next index label (1-based).
226
+ *
227
+ * @return the next index label.
228
+ */
229
+ public int allocate () {
230
+
231
+ if (sequential ) {
232
+ int index = usedLabels .size () + 1 ;
233
+ usedLabels .add (index );
234
+
235
+ return index ;
236
+ }
237
+
238
+ int attempts = usedLabels .last () + 1 ;
239
+ int index = attemptAllocate (attempts );
240
+
241
+ if (index == -1 ) {
242
+ throw new IllegalStateException (
243
+ "Unable to allocate a unique parameter label. All possible labels have been used." );
244
+ }
245
+
246
+ usedLabels .add (index );
247
+
248
+ return index ;
249
+ }
250
+
251
+ private int attemptAllocate (int attempts ) {
252
+
253
+ for (int i = 0 ; i < attempts ; i ++) {
254
+
255
+ if (usedLabels .contains (i + 1 )) {
256
+ continue ;
257
+ }
258
+
259
+ return i + 1 ;
260
+ }
261
+
262
+ return -1 ;
263
+ }
264
+
265
+ public boolean hasLabels () {
266
+ return !usedLabels .isEmpty ();
267
+ }
268
+ }
269
+
194
270
/**
195
271
* A parser that extracts the parameter bindings from a given query string.
196
272
*
@@ -253,28 +329,23 @@ enum ParameterBindingParser {
253
329
String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery (String query , List <ParameterBinding > bindings ,
254
330
Metadata queryMeta ) {
255
331
256
- int greatestParameterIndex = tryFindGreatestParameterIndexIn ( query );
257
- boolean parametersShouldBeAccessedByIndex = greatestParameterIndex != - 1 ;
332
+ IndexedParameterLabels parameterLabels = new IndexedParameterLabels ( findParameterIndices ( query ) );
333
+ boolean parametersShouldBeAccessedByIndex = parameterLabels . hasLabels () ;
258
334
259
335
/*
260
336
* Prefer indexed access over named parameters if only SpEL Expression parameters are present.
261
337
*/
262
338
if (!parametersShouldBeAccessedByIndex && query .contains ("?#{" )) {
263
339
parametersShouldBeAccessedByIndex = true ;
264
- greatestParameterIndex = 0 ;
265
340
}
266
341
267
342
ValueExpressionQueryRewriter .ParsedQuery parsedQuery = createSpelExtractor (query ,
268
- parametersShouldBeAccessedByIndex , greatestParameterIndex );
343
+ parametersShouldBeAccessedByIndex , parameterLabels );
269
344
270
345
String resultingQuery = parsedQuery .getQueryString ();
271
346
Matcher matcher = PARAMETER_BINDING_PATTERN .matcher (resultingQuery );
272
347
273
- int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0 ;
274
- int syntheticParameterIndex = expressionParameterIndex + parsedQuery .size ();
275
-
276
- ParameterBindings parameterBindings = new ParameterBindings (bindings , it -> checkAndRegister (it , bindings ),
277
- syntheticParameterIndex );
348
+ ParameterBindings parameterBindings = new ParameterBindings (bindings , it -> checkAndRegister (it , bindings ));
278
349
int currentIndex = 0 ;
279
350
280
351
boolean usesJpaStyleParameters = false ;
@@ -309,9 +380,9 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que
309
380
.getParameter (parameterName == null ? parameterIndexString : parameterName );
310
381
String replacement = null ;
311
382
312
- expressionParameterIndex ++;
383
+ // this only happens for JDBC-style parameters.
313
384
if ("" .equals (parameterIndexString )) {
314
- parameterIndex = expressionParameterIndex ;
385
+ parameterIndex = parameterLabels . allocate () ;
315
386
}
316
387
317
388
BindingIdentifier queryParameter ;
@@ -346,7 +417,7 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que
346
417
if (origin .isExpression ()) {
347
418
parameterBindings .register (bindingFactory .apply (queryParameter ));
348
419
} else {
349
- targetBinding = parameterBindings .register (queryParameter , origin , bindingFactory );
420
+ targetBinding = parameterBindings .register (queryParameter , origin , bindingFactory , parameterLabels );
350
421
}
351
422
352
423
replacement = targetBinding .hasName () ? ":" + targetBinding .getName ()
@@ -371,16 +442,14 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que
371
442
}
372
443
373
444
private static ValueExpressionQueryRewriter .ParsedQuery createSpelExtractor (String queryWithSpel ,
374
- boolean parametersShouldBeAccessedByIndex , int greatestParameterIndex ) {
445
+ boolean parametersShouldBeAccessedByIndex , IndexedParameterLabels parameterLabels ) {
375
446
376
447
/*
377
448
* 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
378
449
* not mix-up with the actual parameter indices.
379
450
*/
380
- int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0 ;
381
-
382
451
BiFunction <Integer , String , String > indexToParameterName = parametersShouldBeAccessedByIndex
383
- ? (index , expression ) -> String .valueOf (index + expressionParameterIndex + 1 )
452
+ ? (index , expression ) -> String .valueOf (parameterLabels . allocate () )
384
453
: (index , expression ) -> EXPRESSION_PARAMETER_PREFIX + (index + 1 );
385
454
386
455
String fixedPrefix = parametersShouldBeAccessedByIndex ? "?" : ":" ;
@@ -401,21 +470,21 @@ private static Integer getParameterIndex(@Nullable String parameterIndexString)
401
470
return Integer .valueOf (parameterIndexString );
402
471
}
403
472
404
- private static int tryFindGreatestParameterIndexIn (String query ) {
473
+ private static Set < Integer > findParameterIndices (String query ) {
405
474
406
475
Matcher parameterIndexMatcher = PARAMETER_BINDING_BY_INDEX .matcher (query );
476
+ Set <Integer > usedParameterIndices = new TreeSet <>();
407
477
408
- int greatestParameterIndex = -1 ;
409
478
while (parameterIndexMatcher .find ()) {
410
479
411
480
String parameterIndexString = parameterIndexMatcher .group (1 );
412
481
Integer parameterIndex = getParameterIndex (parameterIndexString );
413
482
if (parameterIndex != null ) {
414
- greatestParameterIndex = Math . max ( greatestParameterIndex , parameterIndex );
483
+ usedParameterIndices . add ( parameterIndex );
415
484
}
416
485
}
417
486
418
- return greatestParameterIndex ;
487
+ return usedParameterIndices ;
419
488
}
420
489
421
490
private static void checkAndRegister (ParameterBinding binding , List <ParameterBinding > bindings ) {
@@ -495,17 +564,14 @@ static class ParameterBindings {
495
564
private final MultiValueMap <BindingIdentifier , ParameterBinding > methodArgumentToLikeBindings = new LinkedMultiValueMap <>();
496
565
497
566
private final Consumer <ParameterBinding > registration ;
498
- private int syntheticParameterIndex ;
499
567
500
- public ParameterBindings (List <ParameterBinding > bindings , Consumer <ParameterBinding > registration ,
501
- int syntheticParameterIndex ) {
568
+ public ParameterBindings (List <ParameterBinding > bindings , Consumer <ParameterBinding > registration ) {
502
569
503
570
for (ParameterBinding binding : bindings ) {
504
571
this .methodArgumentToLikeBindings .put (binding .getIdentifier (), new ArrayList <>(List .of (binding )));
505
572
}
506
573
507
574
this .registration = registration ;
508
- this .syntheticParameterIndex = syntheticParameterIndex ;
509
575
}
510
576
511
577
/**
@@ -519,7 +585,7 @@ public boolean isBound(BindingIdentifier identifier) {
519
585
}
520
586
521
587
BindingIdentifier register (BindingIdentifier identifier , ParameterOrigin origin ,
522
- Function <BindingIdentifier , ParameterBinding > bindingFactory ) {
588
+ Function <BindingIdentifier , ParameterBinding > bindingFactory , IndexedParameterLabels parameterLabels ) {
523
589
524
590
Assert .isInstanceOf (MethodInvocationArgument .class , origin );
525
591
@@ -554,7 +620,7 @@ BindingIdentifier register(BindingIdentifier identifier, ParameterOrigin origin,
554
620
}
555
621
syntheticIdentifier = BindingIdentifier .of (newName );
556
622
} else {
557
- syntheticIdentifier = BindingIdentifier .of (++ syntheticParameterIndex );
623
+ syntheticIdentifier = BindingIdentifier .of (parameterLabels . allocate () );
558
624
}
559
625
560
626
ParameterBinding newBinding = bindingFactory .apply (syntheticIdentifier );
0 commit comments