Skip to content

Commit 39216db

Browse files
committed
Fix parameter binding of reused named parameters using anonymous bind markers.
We now properly bind values for reused named parameters correctly when using anonymous bind markers (e.g. ? for MySQL). Previously, the subsequent usages of named parameters especially with IN parameters were left not bound. Closes #778
1 parent b30a9d4 commit 39216db

File tree

2 files changed

+113
-24
lines changed

2 files changed

+113
-24
lines changed

src/main/java/org/springframework/data/r2dbc/core/NamedParameterUtils.java

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.springframework.dao.InvalidDataAccessApiUsageException;
2929
import org.springframework.data.r2dbc.dialect.BindTarget;
30+
import org.springframework.lang.Nullable;
3031
import org.springframework.r2dbc.core.binding.BindMarker;
3132
import org.springframework.r2dbc.core.binding.BindMarkers;
3233
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
@@ -435,6 +436,7 @@ NamedParameter getOrCreate(String namedParameter) {
435436
return param;
436437
}
437438

439+
@Nullable
438440
List<NamedParameter> getMarker(String name) {
439441
return this.references.get(name);
440442
}
@@ -498,36 +500,38 @@ private static class ExpandedQuery implements PreparedOperation<String> {
498500
@SuppressWarnings("unchecked")
499501
public void bind(org.springframework.r2dbc.core.binding.BindTarget target, String identifier, Object value) {
500502

501-
List<BindMarker> bindMarkers = getBindMarkers(identifier);
503+
List<List<BindMarker>> bindMarkers = getBindMarkers(identifier);
502504

503505
if (bindMarkers == null) {
504506

505507
target.bind(identifier, value);
506508
return;
507509
}
508510

509-
if (value instanceof Collection) {
510-
Collection<Object> collection = (Collection<Object>) value;
511+
for (List<BindMarker> outer : bindMarkers) {
512+
if (value instanceof Collection) {
513+
Collection<Object> collection = (Collection<Object>) value;
511514

512-
Iterator<Object> iterator = collection.iterator();
513-
Iterator<BindMarker> markers = bindMarkers.iterator();
515+
Iterator<Object> iterator = collection.iterator();
516+
Iterator<BindMarker> markers = outer.iterator();
514517

515-
while (iterator.hasNext()) {
518+
while (iterator.hasNext()) {
516519

517-
Object valueToBind = iterator.next();
520+
Object valueToBind = iterator.next();
518521

519-
if (valueToBind instanceof Object[]) {
520-
Object[] objects = (Object[]) valueToBind;
521-
for (Object object : objects) {
522-
bind(target, markers, object);
522+
if (valueToBind instanceof Object[]) {
523+
Object[] objects = (Object[]) valueToBind;
524+
for (Object object : objects) {
525+
bind(target, markers, object);
526+
}
527+
} else {
528+
bind(target, markers, valueToBind);
523529
}
524-
} else {
525-
bind(target, markers, valueToBind);
526530
}
527-
}
528-
} else {
529-
for (BindMarker bindMarker : bindMarkers) {
530-
bindMarker.bind(target, value);
531+
} else {
532+
for (BindMarker bindMarker : outer) {
533+
bindMarker.bind(target, value);
534+
}
531535
}
532536
}
533537
}
@@ -546,31 +550,33 @@ private void bind(org.springframework.r2dbc.core.binding.BindTarget target, Iter
546550
public void bindNull(org.springframework.r2dbc.core.binding.BindTarget target, String identifier,
547551
Class<?> valueType) {
548552

549-
List<BindMarker> bindMarkers = getBindMarkers(identifier);
553+
List<List<BindMarker>> bindMarkers = getBindMarkers(identifier);
550554

551555
if (bindMarkers == null) {
552556

553557
target.bindNull(identifier, valueType);
554558
return;
555559
}
556560

557-
for (BindMarker bindMarker : bindMarkers) {
558-
bindMarker.bindNull(target, valueType);
561+
for (List<BindMarker> outer : bindMarkers) {
562+
for (BindMarker bindMarker : outer) {
563+
bindMarker.bindNull(target, valueType);
564+
}
559565
}
560566
}
561567

562-
List<BindMarker> getBindMarkers(String identifier) {
568+
@Nullable
569+
List<List<BindMarker>> getBindMarkers(String identifier) {
563570

564571
List<NamedParameters.NamedParameter> parameters = this.parameters.getMarker(identifier);
565572

566573
if (parameters == null) {
567574
return null;
568575
}
569576

570-
List<BindMarker> markers = new ArrayList<>();
571-
577+
List<List<BindMarker>> markers = new ArrayList<>();
572578
for (NamedParameters.NamedParameter parameter : parameters) {
573-
markers.addAll(parameter.placeholders);
579+
markers.add(new ArrayList<>(parameter.placeholders));
574580
}
575581

576582
return markers;

src/test/java/org/springframework/data/r2dbc/core/NamedParameterUtilsUnitTests.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.mockito.Mockito.*;
2020

21+
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.Collections;
2324
import java.util.HashMap;
2425
import java.util.LinkedHashMap;
26+
import java.util.List;
2527
import java.util.Map;
2628

2729
import org.junit.jupiter.api.Test;
@@ -458,6 +460,87 @@ public void bindNull(int index, Class<?> type) {
458460
});
459461
}
460462

463+
@Test // GH-1306
464+
void inCollectionSameParameterNameShouldBindAllAnonymousParameters() {
465+
466+
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement("select :names AND :names");
467+
org.springframework.r2dbc.core.PreparedOperation<String> operation = NamedParameterUtils
468+
.substituteNamedParameters(parsedSql, BindMarkersFactory.anonymous("?"), new MapBindParameterSource(
469+
Collections.singletonMap("names", SettableValue.from(Arrays.asList("1", "2", "3")))));
470+
471+
List<String> bindings = new ArrayList<>();
472+
473+
operation.bindTo(new BindingCaptor(bindings));
474+
475+
assertThat(operation.get()).isEqualTo("select ?, ?, ? AND ?, ?, ?");
476+
assertThat(bindings).contains("0: 1", "1: 2", "2: 3", "3: 1", "4: 2", "5: 3");
477+
}
478+
479+
@Test // GH-1306
480+
void complexInCollectionSameParameterNameShouldBindAllAnonymousParameters() {
481+
482+
Map<String, SettableValue> parameterMap = new HashMap<>();
483+
parameterMap.put("names", SettableValue.from(Arrays.asList("1", "2", "3")));
484+
parameterMap.put("hello", SettableValue.from("world"));
485+
486+
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement("select :names AND :hello OR :names");
487+
org.springframework.r2dbc.core.PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
488+
parsedSql, BindMarkersFactory.anonymous("?"), new MapBindParameterSource(parameterMap));
489+
490+
List<String> bindings = new ArrayList<>();
491+
492+
operation.bindTo(new BindingCaptor(bindings));
493+
494+
assertThat(operation.get()).isEqualTo("select ?, ?, ? AND ? OR ?, ?, ?");
495+
assertThat(bindings).contains("0: 1", "1: 2", "2: 3", "3: world", "4: 1", "5: 2", "6: 3");
496+
}
497+
498+
@Test // GH-1306
499+
void inCollectionSameParameterNameShouldBindAllNamedParameters() {
500+
501+
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement("select :names AND :names");
502+
org.springframework.r2dbc.core.PreparedOperation<String> operation = NamedParameterUtils
503+
.substituteNamedParameters(parsedSql, BindMarkersFactory.indexed("$", 1), new MapBindParameterSource(
504+
Collections.singletonMap("names", SettableValue.from(Arrays.asList("1", "2", "3")))));
505+
506+
List<String> bindings = new ArrayList<>();
507+
508+
operation.bindTo(new BindingCaptor(bindings));
509+
510+
assertThat(operation.get()).isEqualTo("select $1, $2, $3 AND $1, $2, $3");
511+
assertThat(bindings).containsOnly("0: 1", "1: 2", "2: 3");
512+
}
513+
514+
static class BindingCaptor implements org.springframework.r2dbc.core.binding.BindTarget {
515+
516+
private final List<String> bindings;
517+
518+
BindingCaptor(List<String> bindings) {
519+
this.bindings = bindings;
520+
}
521+
522+
@Override
523+
public void bind(String identifier, Object value) {
524+
bindings.add(identifier + ": " + value);
525+
}
526+
527+
@Override
528+
public void bind(int index, Object value) {
529+
bindings.add(index + ": " + value);
530+
}
531+
532+
@Override
533+
public void bindNull(String identifier, Class<?> type) {
534+
535+
}
536+
537+
@Override
538+
public void bindNull(int index, Class<?> type) {
539+
540+
}
541+
542+
}
543+
461544
private String expand(ParsedSql sql) {
462545
return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS, new MapBindParameterSource()).toQuery();
463546
}

0 commit comments

Comments
 (0)