From de97e35189300824e9663aad77dca47e9afe0dfd Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 9 May 2025 13:58:21 +0200 Subject: [PATCH 1/2] Polish Javadoc and internals of R2DBC support --- .../r2dbc/core/NamedParameterUtils.java | 75 ++++---- .../core/binding/AnonymousBindMarkers.java | 9 +- .../r2dbc/core/binding/BindMarker.java | 11 +- .../r2dbc/core/binding/BindMarkers.java | 8 +- .../binding/BindMarkersFactoryResolver.java | 32 ++-- .../core/binding/IndexedBindMarkers.java | 15 +- .../r2dbc/core/NamedParameterUtilsTests.java | 164 ++++++------------ 7 files changed, 134 insertions(+), 180 deletions(-) diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java index f6696e8d9c..630a00b751 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -513,14 +513,50 @@ abstract class NamedParameterUtils { private final BindParameterSource parameterSource; + ExpandedQuery(String expandedSql, NamedParameters parameters, BindParameterSource parameterSource) { this.expandedSql = expandedSql; this.parameters = parameters; this.parameterSource = parameterSource; } + + @Override + public String toQuery() { + return this.expandedSql; + } + + @Override + public String getSource() { + return this.expandedSql; + } + + @Override + public void bindTo(BindTarget target) { + for (String namedParameter : this.parameterSource.getParameterNames()) { + Parameter parameter = this.parameterSource.getValue(namedParameter); + if (parameter.getValue() == null) { + bindNull(target, namedParameter, parameter); + } + else { + bind(target, namedParameter, parameter); + } + } + } + + private void bindNull(BindTarget target, String identifier, Parameter parameter) { + List bindMarkers = getBindMarkers(identifier); + if (bindMarkers == null) { + target.bind(identifier, parameter); + return; + } + for (BindMarker bindMarker : bindMarkers) { + bindMarker.bind(target, parameter); + } + } + @SuppressWarnings({"rawtypes", "unchecked"}) - public void bind(BindTarget target, String identifier, Parameter parameter) { + private void bind(BindTarget target, String identifier, Parameter parameter) { List bindMarkers = getBindMarkers(identifier); if (bindMarkers == null) { target.bind(identifier, parameter); @@ -555,19 +591,8 @@ abstract class NamedParameterUtils { markers.next().bind(target, valueToBind); } - public void bindNull(BindTarget target, String identifier, Parameter parameter) { - List bindMarkers = getBindMarkers(identifier); - if (bindMarkers == null) { - target.bind(identifier, parameter); - return; - } - for (BindMarker bindMarker : bindMarkers) { - bindMarker.bind(target, parameter); - } - } - @Nullable - List getBindMarkers(String identifier) { + private List getBindMarkers(String identifier) { List parameters = this.parameters.getMarker(identifier); if (parameters == null) { return null; @@ -579,28 +604,6 @@ abstract class NamedParameterUtils { return markers; } - @Override - public String getSource() { - return this.expandedSql; - } - - @Override - public void bindTo(BindTarget target) { - for (String namedParameter : this.parameterSource.getParameterNames()) { - Parameter parameter = this.parameterSource.getValue(namedParameter); - if (parameter.getValue() == null) { - bindNull(target, namedParameter, parameter); - } - else { - bind(target, namedParameter, parameter); - } - } - } - - @Override - public String toQuery() { - return this.expandedSql; - } } } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java index 71569702cc..c66ada2ca1 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,9 @@ package org.springframework.r2dbc.core.binding; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** - * Anonymous, index-based bind marker using a static placeholder. - * Instances are bound by the ordinal position ordered by the appearance of + * Anonymous, index-based bind markers that use a static placeholder. + * + *

Instances are bound by the ordinal position ordered by the appearance of * the placeholder. This implementation creates indexed bind markers using * an anonymous placeholder that correlates with an index. * @@ -46,7 +47,7 @@ class AnonymousBindMarkers implements BindMarkers { /** - * Create a new {@link AnonymousBindMarkers} instance given {@code placeholder}. + * Create a new {@link AnonymousBindMarkers} instance for the given {@code placeholder}. * @param placeholder parameter bind marker */ AnonymousBindMarkers(String placeholder) { diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java index 5fb53c47c1..40f8a71313 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,8 @@ import io.r2dbc.spi.Statement; /** * A bind marker represents a single bindable parameter within a query. - * Bind markers are dialect-specific and provide a + * + *

Bind markers are dialect-specific and provide a * {@link #getPlaceholder() placeholder} that is used in the actual query. * * @author Mark Paluch @@ -37,7 +38,8 @@ public interface BindMarker { String getPlaceholder(); /** - * Bind the given {@code value} to the {@link Statement} using the underlying binding strategy. + * Bind the given {@code value} to the {@link Statement} using the underlying + * binding strategy. * @param bindTarget the target to bind the value to * @param value the actual value (must not be {@code null}; * use {@link #bindNull(BindTarget, Class)} for {@code null} values) @@ -46,7 +48,8 @@ public interface BindMarker { void bind(BindTarget bindTarget, Object value); /** - * Bind a {@code null} value to the {@link Statement} using the underlying binding strategy. + * Bind a {@code null} value to the {@link Statement} using the underlying + * binding strategy. * @param bindTarget the target to bind the value to * @param valueType the value type (must not be {@code null}) * @see Statement#bindNull diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkers.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkers.java index af5a9fd48c..5504cbc01e 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkers.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,10 @@ package org.springframework.r2dbc.core.binding; * Bind markers represent placeholders in SQL queries for substitution * for an actual parameter. Using bind markers allows creating safe queries * so query strings are not required to contain escaped values but rather - * the driver encodes parameter in the appropriate representation. + * the driver encodes the parameter in the appropriate representation. * *

{@link BindMarkers} is stateful and can be only used for a single binding - * pass of one or more parameters. It maintains bind indexes/bind parameter names. + * pass of one or more parameters. It maintains bind indexes or bind parameter names. * * @author Mark Paluch * @since 5.3 @@ -41,7 +41,7 @@ public interface BindMarkers { /** * Create a new {@link BindMarker} that accepts a {@code hint}. - * Implementations are allowed to consider/ignore/filter + *

Implementations are allowed to consider/ignore/filter * the name hint to create more expressive bind markers. * @param hint an optional name hint that can be used as part of the bind marker * @return a new {@link BindMarker} diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactoryResolver.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactoryResolver.java index c658352846..d83c70abdd 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactoryResolver.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactoryResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ import org.springframework.util.LinkedCaseInsensitiveMap; /** * Resolves a {@link BindMarkersFactory} from a {@link ConnectionFactory} using - * {@link BindMarkerFactoryProvider}. Dialect resolution uses Spring's - * {@link SpringFactoriesLoader spring.factories} to determine available extensions. + * a {@link BindMarkerFactoryProvider}. Dialect resolution uses Spring's + * {@link SpringFactoriesLoader spring.factories} file to determine available extensions. * * @author Mark Paluch * @since 5.3 @@ -45,8 +45,8 @@ public final class BindMarkersFactoryResolver { /** - * Retrieve a {@link BindMarkersFactory} by inspecting {@link ConnectionFactory} - * and its metadata. + * Retrieve a {@link BindMarkersFactory} by inspecting the supplied + * {@link ConnectionFactory} and its metadata. * @param connectionFactory the connection factory to inspect * @return the resolved {@link BindMarkersFactory} * @throws NoBindMarkersFactoryException if no {@link BindMarkersFactory} can be resolved @@ -69,18 +69,21 @@ public final class BindMarkersFactoryResolver { /** - * SPI to extend Spring's default R2DBC BindMarkersFactory discovery mechanism. - * Implementations of this interface are discovered through Spring's + * SPI to extend Spring's default R2DBC {@link BindMarkersFactory} discovery + * mechanism. + * + *

Implementations of this interface are discovered through Spring's * {@link SpringFactoriesLoader} mechanism. + * * @see SpringFactoriesLoader */ @FunctionalInterface public interface BindMarkerFactoryProvider { /** - * Return a {@link BindMarkersFactory} for a {@link ConnectionFactory}. - * @param connectionFactory the connection factory to be used with the {@link BindMarkersFactory} - * @return the {@link BindMarkersFactory} if the {@link BindMarkerFactoryProvider} + * Return a {@link BindMarkersFactory} for the given {@link ConnectionFactory}. + * @param connectionFactory the connection factory to be used with the {@code BindMarkersFactory} + * @return the {@code BindMarkersFactory} if this {@code BindMarkerFactoryProvider} * can provide a bind marker factory object, otherwise {@code null} */ @Nullable @@ -89,7 +92,7 @@ public final class BindMarkersFactoryResolver { /** - * Exception thrown when {@link BindMarkersFactoryResolver} cannot resolve a + * Exception thrown when a {@link BindMarkersFactoryResolver} cannot resolve a * {@link BindMarkersFactory}. */ @SuppressWarnings("serial") @@ -106,8 +109,11 @@ public final class BindMarkersFactoryResolver { /** - * Built-in bind maker factories. Used typically as last {@link BindMarkerFactoryProvider} - * when other providers register with a higher precedence. + * Built-in bind marker factories. + * + *

Typically used as the last {@link BindMarkerFactoryProvider} when other + * providers are registered with a higher precedence. + * * @see org.springframework.core.Ordered * @see org.springframework.core.annotation.AnnotationAwareOrderComparator */ diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/IndexedBindMarkers.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/IndexedBindMarkers.java index 5f14d937b4..23081c9778 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/IndexedBindMarkers.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/IndexedBindMarkers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package org.springframework.r2dbc.core.binding; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** - * Index-based bind marker. This implementation creates indexed bind + * Index-based bind markers. This implementation creates indexed bind * markers using a numeric index and an optional prefix for bind markers * to be represented within the query string. * @@ -43,14 +43,15 @@ class IndexedBindMarkers implements BindMarkers { /** - * Create a new {@link IndexedBindMarker} instance given {@code prefix} and {@code beginWith}. - * @param prefix bind parameter prefix - * @param beginWith the first index to use + * Create a new {@link IndexedBindMarker} instance for the given {@code prefix} + * and {@code beginWith} value. + * @param prefix the bind parameter prefix + * @param beginIndex the first index to use */ - IndexedBindMarkers(String prefix, int beginWith) { + IndexedBindMarkers(String prefix, int beginIndex) { this.counter = 0; this.prefix = prefix; - this.offset = beginWith; + this.offset = beginIndex; } diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java index 413c218909..9cd68ee399 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java @@ -28,8 +28,6 @@ import org.junit.jupiter.params.provider.ValueSource; import org.springframework.r2dbc.core.binding.BindMarkersFactory; import org.springframework.r2dbc.core.binding.BindTarget; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -320,34 +318,17 @@ class NamedParameterUtilsTests { assertThat(operation.toQuery()) .isEqualTo("SELECT * FROM person where name = $1 or lastname = $1"); - Map bindings = new HashMap<>(); + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); - operation.bindTo(new BindTarget() { - @Override - public void bind(String identifier, Object value) { - throw new UnsupportedOperationException(); - } - @Override - public void bind(int index, Object value) { - bindings.put(index, value); - } - @Override - public void bindNull(String identifier, Class type) { - throw new UnsupportedOperationException(); - } - @Override - public void bindNull(int index, Class type) { - throw new UnsupportedOperationException(); - } - }); + operation.bindTo(trackingBindTarget); - assertThat(bindings) + assertThat(trackingBindTarget.bindings) .hasSize(1) .containsEntry(0, Parameters.in("foo")); } @Test - void multipleEqualCollectionParameterReferencesForIndexedMarkersBindsValueOnce() { + void multipleEqualCollectionParameterReferencesForIndexedMarkersBindsValuesOnce() { String sql = "SELECT * FROM person where name IN (:ids) or lastname IN (:ids)"; MapBindParameterSource source = new MapBindParameterSource(Map.of("ids", @@ -357,71 +338,40 @@ class NamedParameterUtilsTests { assertThat(operation.toQuery()) .isEqualTo("SELECT * FROM person where name IN ($1, $2, $3) or lastname IN ($1, $2, $3)"); - MultiValueMap bindings = new LinkedMultiValueMap<>(); + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); - operation.bindTo(new BindTarget() { - @Override - public void bind(String identifier, Object value) { - throw new UnsupportedOperationException(); - } - @Override - public void bind(int index, Object value) { - bindings.add(index, value); - } - @Override - public void bindNull(String identifier, Class type) { - throw new UnsupportedOperationException(); - } - @Override - public void bindNull(int index, Class type) { - throw new UnsupportedOperationException(); - } - }); + operation.bindTo(trackingBindTarget); - assertThat(bindings) + assertThat(trackingBindTarget.bindings) .hasSize(3) - .containsEntry(0, List.of("foo")) - .containsEntry(1, List.of("bar")) - .containsEntry(2, List.of("baz")); + .containsEntry(0, "foo") + .containsEntry(1, "bar") + .containsEntry(2, "baz"); } @Test // gh-34768 @Disabled("Disabled until gh-34768 is addressed") - void multipleEqualCollectionParameterReferencesForAnonymousMarkersBindsValueTwice() { + void multipleEqualCollectionParameterReferencesForAnonymousMarkersBindsValuesTwice() { String sql = "SELECT * FROM fund_info WHERE fund_code IN (:fundCodes) OR fund_code IN (:fundCodes)"; - MapBindParameterSource source = new MapBindParameterSource(Map.of("fundCodes", Parameters.in(List.of("foo")))); + MapBindParameterSource source = new MapBindParameterSource(Map.of("fundCodes", Parameters.in(List.of("foo", "bar", "baz")))); PreparedOperation operation = NamedParameterUtils.substituteNamedParameters(sql, ANONYMOUS_MARKERS, source); assertThat(operation.toQuery()) - .isEqualTo("SELECT * FROM fund_info WHERE fund_code IN (?) OR fund_code IN (?)"); + .isEqualTo("SELECT * FROM fund_info WHERE fund_code IN (?, ?, ?) OR fund_code IN (?, ?, ?)"); - Map bindings = new HashMap<>(); + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); - operation.bindTo(new BindTarget() { - @Override - public void bind(String identifier, Object value) {} + operation.bindTo(trackingBindTarget); - @Override - public void bind(int index, Object value) { - bindings.put(index, value); - } - - @Override - public void bindNull(String identifier, Class type) { - throw new UnsupportedOperationException(); - } - - @Override - public void bindNull(int index, Class type) { - throw new UnsupportedOperationException(); - } - }); - - assertThat(bindings) - .hasSize(2) + assertThat(trackingBindTarget.bindings) + .hasSize(6) .containsEntry(0, "foo") - .containsEntry(1, "foo"); + .containsEntry(1, "bar") + .containsEntry(2, "baz") + .containsEntry(3, "foo") + .containsEntry(4, "bar") + .containsEntry(5, "baz"); } @Test @@ -434,28 +384,11 @@ class NamedParameterUtilsTests { assertThat(operation.toQuery()) .isEqualTo("SELECT * FROM person where name = ? or lastname = ?"); - Map bindings = new HashMap<>(); + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); - operation.bindTo(new BindTarget() { - @Override - public void bind(String identifier, Object value) { - throw new UnsupportedOperationException(); - } - @Override - public void bind(int index, Object value) { - bindings.put(index, value); - } - @Override - public void bindNull(String identifier, Class type) { - throw new UnsupportedOperationException(); - } - @Override - public void bindNull(int index, Class type) { - throw new UnsupportedOperationException(); - } - }); + operation.bindTo(trackingBindTarget); - assertThat(bindings) + assertThat(trackingBindTarget.bindings) .hasSize(2) .containsEntry(0, Parameters.in("foo")) .containsEntry(1, Parameters.in("foo")); @@ -471,28 +404,11 @@ class NamedParameterUtilsTests { assertThat(operation.toQuery()) .isEqualTo("SELECT * FROM person where name = $1 or lastname = $1"); - Map bindings = new HashMap<>(); + TrackingBindTarget trackingBindTarget = new TrackingBindTarget(); - operation.bindTo(new BindTarget() { - @Override - public void bind(String identifier, Object value) { - throw new UnsupportedOperationException(); - } - @Override - public void bind(int index, Object value) { - bindings.put(index, value); - } - @Override - public void bindNull(String identifier, Class type) { - throw new UnsupportedOperationException(); - } - @Override - public void bindNull(int index, Class type) { - throw new UnsupportedOperationException(); - } - }); + operation.bindTo(trackingBindTarget); - assertThat(bindings) + assertThat(trackingBindTarget.bindings) .hasSize(1) .containsEntry(0, Parameters.in(String.class)); } @@ -508,4 +424,28 @@ class NamedParameterUtilsTests { new MapBindParameterSource()).toQuery(); } + + private static class TrackingBindTarget implements BindTarget { + + final Map bindings = new HashMap<>(); + + @Override + public void bind(String identifier, Object value) {} + + @Override + public void bind(int index, Object value) { + this.bindings.put(index, value); + } + + @Override + public void bindNull(String identifier, Class type) { + throw new UnsupportedOperationException(); + } + + @Override + public void bindNull(int index, Class type) { + throw new UnsupportedOperationException(); + } + } + } From dd6eede243b92dbbbc3790516f2a439f2941a8f4 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 9 May 2025 14:27:19 +0200 Subject: [PATCH 2/2] Properly expand reused collection parameters in R2DBC NamedParameterUtils Prior to this commit, NamedParameterUtils in spring-r2dbc did not properly expand reused collection parameters. Specifically, values in a supplied collection were only expanded in the resulting query once, for the first occurrence of the named parameter. To address that, this commit effectively reinstates the original logic for ExpandedQuery from NamedParameterUtils in the Spring Data R2DBC project. https://github.com/spring-projects/spring-data-relational/blob/94958f5eb66cbe2e8e025155cd99abf36f6f91f4/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/NamedParameterUtils.java#L486 Closes gh-34768 --- .../r2dbc/core/NamedParameterUtils.java | 49 ++++++++++--------- .../r2dbc/core/NamedParameterUtilsTests.java | 2 - 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java index 630a00b751..76b7adfcb8 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java @@ -49,6 +49,7 @@ import org.springframework.util.Assert; * @author Juergen Hoeller * @author Mark Paluch * @author Anton Naydenov + * @author Sam Brannen * @since 5.3 */ abstract class NamedParameterUtils { @@ -545,41 +546,43 @@ abstract class NamedParameterUtils { } private void bindNull(BindTarget target, String identifier, Parameter parameter) { - List bindMarkers = getBindMarkers(identifier); + List> bindMarkers = getBindMarkers(identifier); if (bindMarkers == null) { target.bind(identifier, parameter); return; } - for (BindMarker bindMarker : bindMarkers) { - bindMarker.bind(target, parameter); + for (List outer : bindMarkers) { + for (BindMarker bindMarker : outer) { + bindMarker.bind(target, parameter); + } } } - @SuppressWarnings({"rawtypes", "unchecked"}) private void bind(BindTarget target, String identifier, Parameter parameter) { - List bindMarkers = getBindMarkers(identifier); + List> bindMarkers = getBindMarkers(identifier); if (bindMarkers == null) { target.bind(identifier, parameter); return; } - if (parameter.getValue() instanceof Collection collection) { - Iterator iterator = collection.iterator(); - Iterator markers = bindMarkers.iterator(); - while (iterator.hasNext()) { - Object valueToBind = iterator.next(); - if (valueToBind instanceof Object[] objects) { - for (Object object : objects) { - bind(target, markers, object); + + for (List outer : bindMarkers) { + if (parameter.getValue() instanceof Collection collection) { + Iterator markers = outer.iterator(); + for (Object valueToBind : collection) { + if (valueToBind instanceof Object[] objects) { + for (Object object : objects) { + bind(target, markers, object); + } + } + else { + bind(target, markers, valueToBind); } } - else { - bind(target, markers, valueToBind); - } } - } - else { - for (BindMarker bindMarker : bindMarkers) { - bindMarker.bind(target, parameter); + else { + for (BindMarker bindMarker : outer) { + bindMarker.bind(target, parameter); + } } } } @@ -592,14 +595,14 @@ abstract class NamedParameterUtils { } @Nullable - private List getBindMarkers(String identifier) { + private List> getBindMarkers(String identifier) { List parameters = this.parameters.getMarker(identifier); if (parameters == null) { return null; } - List markers = new ArrayList<>(); + List> markers = new ArrayList<>(); for (NamedParameters.NamedParameter parameter : parameters) { - markers.addAll(parameter.placeholders); + markers.add(new ArrayList<>(parameter.placeholders)); } return markers; } diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java index 9cd68ee399..725bcf72a9 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/NamedParameterUtilsTests.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import io.r2dbc.spi.Parameters; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -350,7 +349,6 @@ class NamedParameterUtilsTests { } @Test // gh-34768 - @Disabled("Disabled until gh-34768 is addressed") void multipleEqualCollectionParameterReferencesForAnonymousMarkersBindsValuesTwice() { String sql = "SELECT * FROM fund_info WHERE fund_code IN (:fundCodes) OR fund_code IN (:fundCodes)";