3131import org .springframework .data .r2dbc .dialect .BindMarkersFactory ;
3232import org .springframework .data .r2dbc .dialect .BindTarget ;
3333import org .springframework .util .Assert ;
34+ import org .springframework .util .CollectionUtils ;
3435
3536/**
3637 * Helper methods for named parameter parsing.
3738 * <p>
3839 * Only intended for internal use within Spring's Data's R2DBC framework. Partially extracted from Spring's JDBC named
3940 * parameter support.
4041 * <p>
42+ * References to the same parameter name are substituted with the same bind marker placeholder if a
43+ * {@link BindMarkersFactory} uses {@link BindMarkersFactory#identifiablePlaceholders() identifiable} placeholders.
44+ * <p>
4145 * This is a subset of Spring Frameworks's {@code org.springframework.r2dbc.namedparam.NamedParameterUtils}.
4246 *
4347 * @author Thomas Risberg
@@ -260,7 +264,7 @@ private static int skipCommentsAndQuotes(char[] statement, int position) {
260264 public static PreparedOperation <String > substituteNamedParameters (ParsedSql parsedSql ,
261265 BindMarkersFactory bindMarkersFactory , BindParameterSource paramSource ) {
262266
263- BindMarkerHolder markerHolder = new BindMarkerHolder (bindMarkersFactory . create () );
267+ NamedParameters markerHolder = new NamedParameters (bindMarkersFactory );
264268
265269 String originalSql = parsedSql .getOriginalSql ();
266270 List <String > paramNames = parsedSql .getParameterNames ();
@@ -276,9 +280,11 @@ public static PreparedOperation<String> substituteNamedParameters(ParsedSql pars
276280 int startIndex = indexes [0 ];
277281 int endIndex = indexes [1 ];
278282 actualSql .append (originalSql , lastIndex , startIndex );
283+ NamedParameters .NamedParameter marker = markerHolder .getOrCreate (paramName );
279284 if (paramSource .hasValue (paramName )) {
280285 Object value = paramSource .getValue (paramName );
281286 if (value instanceof Collection ) {
287+
282288 Iterator <?> entryIter = ((Collection <?>) value ).iterator ();
283289 int k = 0 ;
284290 while (entryIter .hasNext ()) {
@@ -294,19 +300,19 @@ public static PreparedOperation<String> substituteNamedParameters(ParsedSql pars
294300 if (m > 0 ) {
295301 actualSql .append (", " );
296302 }
297- actualSql .append (markerHolder . addMarker ( paramName ));
303+ actualSql .append (marker . addPlaceholder ( ));
298304 }
299305 actualSql .append (')' );
300306 } else {
301- actualSql .append (markerHolder . addMarker ( paramName ));
307+ actualSql .append (marker . addPlaceholder ( ));
302308 }
303309
304310 }
305311 } else {
306- actualSql .append (markerHolder . addMarker ( paramName ));
312+ actualSql .append (marker . getPlaceholder ( ));
307313 }
308314 } else {
309- actualSql .append (markerHolder . addMarker ( paramName ));
315+ actualSql .append (marker . getPlaceholder ( ));
310316 }
311317 lastIndex = endIndex ;
312318 }
@@ -387,22 +393,79 @@ public int hashCode() {
387393 }
388394
389395 /**
390- * Holder for bind marker progress.
396+ * Holder for bind markers progress.
391397 */
392- private static class BindMarkerHolder {
398+ static class NamedParameters {
393399
394400 private final BindMarkers bindMarkers ;
395- private final Map <String , List <BindMarker >> markers = new TreeMap <>();
401+ private final boolean identifiable ;
402+ private final Map <String , List <NamedParameter >> references = new TreeMap <>();
403+
404+ NamedParameters (BindMarkersFactory factory ) {
405+ this .bindMarkers = factory .create ();
406+ this .identifiable = factory .identifiablePlaceholders ();
407+ }
408+
409+ /**
410+ * Get the {@link NamedParameter} identified by {@code namedParameter}.
411+ *
412+ * @param namedParameter
413+ * @return
414+ */
415+ NamedParameter getOrCreate (String namedParameter ) {
416+
417+ List <NamedParameter > reference = this .references .computeIfAbsent (namedParameter , ignore -> new ArrayList <>());
418+
419+ if (reference .isEmpty ()) {
420+ NamedParameter param = new NamedParameter (namedParameter );
421+ reference .add (param );
422+ return param ;
423+ }
396424
397- BindMarkerHolder (BindMarkers bindMarkers ) {
398- this .bindMarkers = bindMarkers ;
425+ if (this .identifiable ) {
426+ return reference .get (0 );
427+ }
428+
429+ NamedParameter param = new NamedParameter (namedParameter );
430+ reference .add (param );
431+ return param ;
399432 }
400433
401- String addMarker (String name ) {
434+ List <NamedParameter > getMarker (String name ) {
435+ return this .references .get (name );
436+ }
437+
438+ class NamedParameter {
439+
440+ private final String namedParameter ;
441+ private final List <BindMarker > placeholders = new ArrayList <>();
442+
443+ NamedParameter (String namedParameter ) {
444+ this .namedParameter = namedParameter ;
445+ }
446+
447+ /**
448+ * Create a placeholder to translate a single value into a bindable parameter.
449+ * <p>
450+ * Can be called multiple times to create placeholders for array/collections.
451+ *
452+ * @return
453+ */
454+ String addPlaceholder () {
455+
456+ BindMarker bindMarker = NamedParameters .this .bindMarkers .next (this .namedParameter );
457+ this .placeholders .add (bindMarker );
458+ return bindMarker .getPlaceholder ();
459+ }
460+
461+ String getPlaceholder () {
462+
463+ if (this .placeholders .isEmpty ()) {
464+ return addPlaceholder ();
465+ }
402466
403- BindMarker bindMarker = this .bindMarkers .next (name );
404- this .markers .computeIfAbsent (name , ignore -> new ArrayList <>()).add (bindMarker );
405- return bindMarker .getPlaceholder ();
467+ return this .placeholders .get (0 ).getPlaceholder ();
468+ }
406469 }
407470 }
408471
@@ -414,13 +477,13 @@ private static class ExpandedQuery implements PreparedOperation<String> {
414477
415478 private final String expandedSql ;
416479
417- private final Map < String , List < BindMarker >> markers ;
480+ private final NamedParameters parameters ;
418481
419482 private final BindParameterSource parameterSource ;
420483
421- ExpandedQuery (String expandedSql , BindMarkerHolder bindMarkerHolder , BindParameterSource parameterSource ) {
484+ ExpandedQuery (String expandedSql , NamedParameters parameters , BindParameterSource parameterSource ) {
422485 this .expandedSql = expandedSql ;
423- this .markers = bindMarkerHolder . markers ;
486+ this .parameters = parameters ;
424487 this .parameterSource = parameterSource ;
425488 }
426489
@@ -435,13 +498,7 @@ public void bind(BindTarget target, String identifier, Object value) {
435498 return ;
436499 }
437500
438- if (bindMarkers .size () == 1 ) {
439- bindMarkers .get (0 ).bind (target , value );
440- } else {
441-
442- Assert .isInstanceOf (Collection .class , value ,
443- () -> String .format ("Value [%s] must be an Collection with a size of [%d]" , value , bindMarkers .size ()));
444-
501+ if (value instanceof Collection ) {
445502 Collection <Object > collection = (Collection <Object >) value ;
446503
447504 Iterator <Object > iterator = collection .iterator ();
@@ -460,6 +517,10 @@ public void bind(BindTarget target, String identifier, Object value) {
460517 bind (target , markers , valueToBind );
461518 }
462519 }
520+ } else {
521+ for (BindMarker bindMarker : bindMarkers ) {
522+ bindMarker .bind (target , value );
523+ }
463524 }
464525 }
465526
@@ -483,16 +544,26 @@ public void bindNull(BindTarget target, String identifier, Class<?> valueType) {
483544 return ;
484545 }
485546
486- if (bindMarkers .size () == 1 ) {
487- bindMarkers .get (0 ).bindNull (target , valueType );
488- return ;
547+ for (BindMarker bindMarker : bindMarkers ) {
548+ bindMarker .bindNull (target , valueType );
489549 }
490-
491- throw new UnsupportedOperationException ("bindNull(…) can bind only singular values" );
492550 }
493551
494- private List <BindMarker > getBindMarkers (String identifier ) {
495- return this .markers .get (identifier );
552+ List <BindMarker > getBindMarkers (String identifier ) {
553+
554+ List <NamedParameters .NamedParameter > parameters = this .parameters .getMarker (identifier );
555+
556+ if (parameters == null ) {
557+ return null ;
558+ }
559+
560+ List <BindMarker > markers = new ArrayList <>();
561+
562+ for (NamedParameters .NamedParameter parameter : parameters ) {
563+ markers .addAll (parameter .placeholders );
564+ }
565+
566+ return markers ;
496567 }
497568
498569 @ Override
0 commit comments