11/*
2- * Copyright 2002-2024 the original author or authors.
2+ * Copyright 2002-2025 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1616
1717package org .springframework .r2dbc .core ;
1818
19- import java .util .Arrays ;
20- import java .util .Collections ;
2119import java .util .HashMap ;
22- import java .util .LinkedHashMap ;
20+ import java .util .List ;
2321import java .util .Map ;
2422
2523import io .r2dbc .spi .Parameters ;
24+ import org .junit .jupiter .api .Disabled ;
2625import org .junit .jupiter .api .Test ;
2726import org .junit .jupiter .params .ParameterizedTest ;
2827import org .junit .jupiter .params .provider .ValueSource ;
4241 * @author Mark Paluch
4342 * @author Jens Schauder
4443 * @author Anton Naydenov
44+ * @author Sam Brannen
4545 */
4646class NamedParameterUtilsTests {
4747
48- private final BindMarkersFactory BIND_MARKERS = BindMarkersFactory .indexed ("$" , 1 );
48+ private static final BindMarkersFactory INDEXED_MARKERS = BindMarkersFactory .indexed ("$" , 1 );
49+
50+ private static final BindMarkersFactory ANONYMOUS_MARKERS = BindMarkersFactory .anonymous ("?" );
4951
5052
5153 @ Test
@@ -73,7 +75,7 @@ void substituteNamedParameters() {
7375 namedParams .addValue ("a" , "a" ).addValue ("b" , "b" ).addValue ("c" , "c" );
7476
7577 PreparedOperation <?> operation = NamedParameterUtils .substituteNamedParameters (
76- "xxx :a :b :c" , BIND_MARKERS , namedParams );
78+ "xxx :a :b :c" , INDEXED_MARKERS , namedParams );
7779
7880 assertThat (operation .toQuery ()).isEqualTo ("xxx $1 $2 $3" );
7981
@@ -87,11 +89,11 @@ void substituteNamedParameters() {
8789 void substituteObjectArray () {
8890 MapBindParameterSource namedParams = new MapBindParameterSource (new HashMap <>());
8991 namedParams .addValue ("a" ,
90- Arrays . asList (new Object [] {"Walter" , "Heisenberg" },
91- new Object [] {"Walt Jr." , "Flynn" }));
92+ List . of (new Object [] {"Walter" , "Heisenberg" },
93+ new Object [] {"Walt Jr." , "Flynn" }));
9294
9395 PreparedOperation <?> operation = NamedParameterUtils .substituteNamedParameters (
94- "xxx :a" , BIND_MARKERS , namedParams );
96+ "xxx :a" , INDEXED_MARKERS , namedParams );
9597
9698 assertThat (operation .toQuery ()).isEqualTo ("xxx ($1, $2), ($3, $4)" );
9799 }
@@ -100,13 +102,13 @@ void substituteObjectArray() {
100102 void shouldBindObjectArray () {
101103 MapBindParameterSource namedParams = new MapBindParameterSource (new HashMap <>());
102104 namedParams .addValue ("a" ,
103- Arrays . asList (new Object [] {"Walter" , "Heisenberg" },
104- new Object [] {"Walt Jr." , "Flynn" }));
105+ List . of (new Object [] {"Walter" , "Heisenberg" },
106+ new Object [] {"Walt Jr." , "Flynn" }));
105107
106108 BindTarget bindTarget = mock ();
107109
108110 PreparedOperation <?> operation = NamedParameterUtils .substituteNamedParameters (
109- "xxx :a" , BIND_MARKERS , namedParams );
111+ "xxx :a" , INDEXED_MARKERS , namedParams );
110112 operation .bindTo (bindTarget );
111113
112114 verify (bindTarget ).bind (0 , "Walter" );
@@ -141,7 +143,7 @@ void parseSqlStatementWithPostgresCasting() {
141143
142144 ParsedSql parsedSql = NamedParameterUtils .parseSqlStatement (sql );
143145 PreparedOperation <?> operation = NamedParameterUtils .substituteNamedParameters (
144- parsedSql , BIND_MARKERS , new MapBindParameterSource ());
146+ parsedSql , INDEXED_MARKERS , new MapBindParameterSource ());
145147
146148 assertThat (operation .toQuery ()).isEqualTo (expectedSql );
147149 }
@@ -312,14 +314,13 @@ void shouldAllowParsingMultipleUseOfParameter() {
312314 void multipleEqualParameterReferencesBindsValueOnce () {
313315 String sql = "SELECT * FROM person where name = :id or lastname = :id" ;
314316
315- BindMarkersFactory factory = BindMarkersFactory .indexed ("$" , 0 );
317+ MapBindParameterSource source = new MapBindParameterSource (Map .of ("id" , Parameters .in ("foo" )));
318+ PreparedOperation <String > operation = NamedParameterUtils .substituteNamedParameters (sql , INDEXED_MARKERS , source );
316319
317- PreparedOperation <String > operation = NamedParameterUtils .substituteNamedParameters (
318- sql , factory , new MapBindParameterSource (
319- Collections .singletonMap ("id" , Parameters .in ("foo" ))));
320+ assertThat (operation .toQuery ())
321+ .isEqualTo ("SELECT * FROM person where name = $1 or lastname = $1" );
320322
321- assertThat (operation .toQuery ()).isEqualTo (
322- "SELECT * FROM person where name = $0 or lastname = $0" );
323+ Map <Integer , Object > bindings = new HashMap <>();
323324
324325 operation .bindTo (new BindTarget () {
325326 @ Override
@@ -328,8 +329,7 @@ public void bind(String identifier, Object value) {
328329 }
329330 @ Override
330331 public void bind (int index , Object value ) {
331- assertThat (index ).isEqualTo (0 );
332- assertThat (value ).isEqualTo (Parameters .in ("foo" ));
332+ bindings .put (index , value );
333333 }
334334 @ Override
335335 public void bindNull (String identifier , Class <?> type ) {
@@ -340,22 +340,24 @@ public void bindNull(int index, Class<?> type) {
340340 throw new UnsupportedOperationException ();
341341 }
342342 });
343+
344+ assertThat (bindings )
345+ .hasSize (1 )
346+ .containsEntry (0 , Parameters .in ("foo" ));
343347 }
344348
345349 @ Test
346- void multipleEqualCollectionParameterReferencesBindsValueOnce () {
350+ void multipleEqualCollectionParameterReferencesForIndexedMarkersBindsValueOnce () {
347351 String sql = "SELECT * FROM person where name IN (:ids) or lastname IN (:ids)" ;
348352
349- BindMarkersFactory factory = BindMarkersFactory . indexed ( "$" , 0 );
350-
351- MultiValueMap < Integer , Object > bindings = new LinkedMultiValueMap <>( );
353+ MapBindParameterSource source = new MapBindParameterSource ( Map . of ( "ids" ,
354+ Parameters . in ( List . of ( "foo" , "bar" , "baz" ))));
355+ PreparedOperation < String > operation = NamedParameterUtils . substituteNamedParameters ( sql , INDEXED_MARKERS , source );
352356
353- PreparedOperation <String > operation = NamedParameterUtils .substituteNamedParameters (
354- sql , factory , new MapBindParameterSource (Collections .singletonMap ("ids" ,
355- Parameters .in (Arrays .asList ("foo" , "bar" , "baz" )))));
357+ assertThat (operation .toQuery ())
358+ .isEqualTo ("SELECT * FROM person where name IN ($1, $2, $3) or lastname IN ($1, $2, $3)" );
356359
357- assertThat (operation .toQuery ()).isEqualTo (
358- "SELECT * FROM person where name IN ($0, $1, $2) or lastname IN ($0, $1, $2)" );
360+ MultiValueMap <Integer , Object > bindings = new LinkedMultiValueMap <>();
359361
360362 operation .bindTo (new BindTarget () {
361363 @ Override
@@ -364,8 +366,6 @@ public void bind(String identifier, Object value) {
364366 }
365367 @ Override
366368 public void bind (int index , Object value ) {
367- assertThat (index ).isIn (0 , 1 , 2 );
368- assertThat (value ).isIn ("foo" , "bar" , "baz" );
369369 bindings .add (index , value );
370370 }
371371 @ Override
@@ -378,25 +378,63 @@ public void bindNull(int index, Class<?> type) {
378378 }
379379 });
380380
381- assertThat (bindings ).containsEntry (0 , Collections .singletonList ("foo" )) //
382- .containsEntry (1 , Collections .singletonList ("bar" )) //
383- .containsEntry (2 , Collections .singletonList ("baz" ));
381+ assertThat (bindings )
382+ .hasSize (3 )
383+ .containsEntry (0 , List .of ("foo" ))
384+ .containsEntry (1 , List .of ("bar" ))
385+ .containsEntry (2 , List .of ("baz" ));
386+ }
387+
388+ @ Test // gh-34768
389+ @ Disabled ("Disabled until gh-34768 is addressed" )
390+ void multipleEqualCollectionParameterReferencesForAnonymousMarkersBindsValueTwice () {
391+ String sql = "SELECT * FROM fund_info WHERE fund_code IN (:fundCodes) OR fund_code IN (:fundCodes)" ;
392+
393+ MapBindParameterSource source = new MapBindParameterSource (Map .of ("fundCodes" , Parameters .in (List .of ("foo" ))));
394+ PreparedOperation <String > operation = NamedParameterUtils .substituteNamedParameters (sql , ANONYMOUS_MARKERS , source );
395+
396+ assertThat (operation .toQuery ())
397+ .isEqualTo ("SELECT * FROM fund_info WHERE fund_code IN (?) OR fund_code IN (?)" );
398+
399+ Map <Integer , Object > bindings = new HashMap <>();
400+
401+ operation .bindTo (new BindTarget () {
402+ @ Override
403+ public void bind (String identifier , Object value ) {}
404+
405+ @ Override
406+ public void bind (int index , Object value ) {
407+ bindings .put (index , value );
408+ }
409+
410+ @ Override
411+ public void bindNull (String identifier , Class <?> type ) {
412+ throw new UnsupportedOperationException ();
413+ }
414+
415+ @ Override
416+ public void bindNull (int index , Class <?> type ) {
417+ throw new UnsupportedOperationException ();
418+ }
419+ });
420+
421+ assertThat (bindings )
422+ .hasSize (2 )
423+ .containsEntry (0 , "foo" )
424+ .containsEntry (1 , "foo" );
384425 }
385426
386427 @ Test
387- void multipleEqualParameterReferencesForAnonymousMarkersBindsValueMultipleTimes () {
428+ void multipleEqualParameterReferencesForAnonymousMarkersBindsValueTwice () {
388429 String sql = "SELECT * FROM person where name = :id or lastname = :id" ;
389430
390- BindMarkersFactory factory = BindMarkersFactory .anonymous ("?" );
391-
392- PreparedOperation <String > operation = NamedParameterUtils .substituteNamedParameters (
393- sql , factory , new MapBindParameterSource (
394- Collections .singletonMap ("id" , Parameters .in ("foo" ))));
431+ MapBindParameterSource source = new MapBindParameterSource (Map .of ("id" , Parameters .in ("foo" )));
432+ PreparedOperation <String > operation = NamedParameterUtils .substituteNamedParameters (sql , ANONYMOUS_MARKERS , source );
395433
396- assertThat (operation .toQuery ()). isEqualTo (
397- "SELECT * FROM person where name = ? or lastname = ?" );
434+ assertThat (operation .toQuery ())
435+ . isEqualTo ( "SELECT * FROM person where name = ? or lastname = ?" );
398436
399- Map <Integer , Object > bindValues = new LinkedHashMap <>();
437+ Map <Integer , Object > bindings = new HashMap <>();
400438
401439 operation .bindTo (new BindTarget () {
402440 @ Override
@@ -405,7 +443,7 @@ public void bind(String identifier, Object value) {
405443 }
406444 @ Override
407445 public void bind (int index , Object value ) {
408- bindValues .put (index , value );
446+ bindings .put (index , value );
409447 }
410448 @ Override
411449 public void bindNull (String identifier , Class <?> type ) {
@@ -417,21 +455,23 @@ public void bindNull(int index, Class<?> type) {
417455 }
418456 });
419457
420- assertThat (bindValues ).hasSize (2 ).containsEntry (0 , Parameters .in ("foo" )).containsEntry (1 , Parameters .in ("foo" ));
458+ assertThat (bindings )
459+ .hasSize (2 )
460+ .containsEntry (0 , Parameters .in ("foo" ))
461+ .containsEntry (1 , Parameters .in ("foo" ));
421462 }
422463
423464 @ Test
424465 void multipleEqualParameterReferencesBindsNullOnce () {
425466 String sql = "SELECT * FROM person where name = :id or lastname = :id" ;
426467
427- BindMarkersFactory factory = BindMarkersFactory .indexed ("$" , 0 );
468+ MapBindParameterSource source = new MapBindParameterSource (Map .of ("id" , Parameters .in (String .class )));
469+ PreparedOperation <String > operation = NamedParameterUtils .substituteNamedParameters (sql , INDEXED_MARKERS , source );
428470
429- PreparedOperation <String > operation = NamedParameterUtils .substituteNamedParameters (
430- sql , factory , new MapBindParameterSource (
431- Collections .singletonMap ("id" , Parameters .in (String .class ))));
471+ assertThat (operation .toQuery ())
472+ .isEqualTo ("SELECT * FROM person where name = $1 or lastname = $1" );
432473
433- assertThat (operation .toQuery ()).isEqualTo (
434- "SELECT * FROM person where name = $0 or lastname = $0" );
474+ Map <Integer , Object > bindings = new HashMap <>();
435475
436476 operation .bindTo (new BindTarget () {
437477 @ Override
@@ -440,8 +480,7 @@ public void bind(String identifier, Object value) {
440480 }
441481 @ Override
442482 public void bind (int index , Object value ) {
443- assertThat (index ).isEqualTo (0 );
444- assertThat (value ).isEqualTo (Parameters .in (String .class ));
483+ bindings .put (index , value );
445484 }
446485 @ Override
447486 public void bindNull (String identifier , Class <?> type ) {
@@ -452,16 +491,20 @@ public void bindNull(int index, Class<?> type) {
452491 throw new UnsupportedOperationException ();
453492 }
454493 });
494+
495+ assertThat (bindings )
496+ .hasSize (1 )
497+ .containsEntry (0 , Parameters .in (String .class ));
455498 }
456499
457500
458- private String expand (ParsedSql sql ) {
459- return NamedParameterUtils .substituteNamedParameters (sql , BIND_MARKERS ,
501+ private static String expand (ParsedSql sql ) {
502+ return NamedParameterUtils .substituteNamedParameters (sql , INDEXED_MARKERS ,
460503 new MapBindParameterSource ()).toQuery ();
461504 }
462505
463- private String expand (String sql ) {
464- return NamedParameterUtils .substituteNamedParameters (sql , BIND_MARKERS ,
506+ private static String expand (String sql ) {
507+ return NamedParameterUtils .substituteNamedParameters (sql , INDEXED_MARKERS ,
465508 new MapBindParameterSource ()).toQuery ();
466509 }
467510
0 commit comments