diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java index 99daa91494a..a78e31a1f8a 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.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. @@ -16,6 +16,8 @@ package org.springframework.jdbc.core.simple; +import java.util.List; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -143,6 +145,52 @@ class JdbcClientIntegrationTests { assertUser(expectedId, firstName, lastName); } + @Test // gh-34768 + void selectWithReusedNamedParameter() { + this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "John").update(); + this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "Smith").update(); + this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("Smith", "Smith").update(); + assertNumUsers(4); + + String sql = """ + select * from users + where + first_name in ('Bogus', :name) or + last_name in (:name, 'Bogus') + order by last_name + """; + + List users = this.jdbcClient.sql(sql) + .param("name", "John") + .query(User.class) + .list(); + + assertThat(users).containsExactly(new User(2, "John", "John"), new User(3, "John", "Smith")); + } + + @Test // gh-34768 + void selectWithReusedNamedParameterList() { + this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "John").update(); + this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "Smith").update(); + this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("Smith", "Smith").update(); + assertNumUsers(4); + + String sql = """ + select * from users + where + first_name in (:names) or + last_name in (:names) + order by last_name + """; + + List users = this.jdbcClient.sql(sql) + .param("names", List.of("John", "Bogus")) + .query(User.class) + .list(); + + assertThat(users).containsExactly(new User(2, "John", "John"), new User(3, "John", "Smith")); + } + private void assertNumUsers(long count) { long numUsers = this.jdbcClient.sql("select count(id) from users").query(Long.class).single(); diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/AbstractDatabaseClientIntegrationTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/AbstractDatabaseClientIntegrationTests.java index 19332ecd403..b6a91fbcf77 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/AbstractDatabaseClientIntegrationTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/AbstractDatabaseClientIntegrationTests.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. @@ -211,6 +211,69 @@ abstract class AbstractDatabaseClientIntegrationTests { .verifyComplete(); } + @Test // gh-34768 + void executeInsertWithReusedNamedParameter() { + DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); + + Lego lego = new Lego(1, 42, "Star Wars", 42); + + databaseClient.sql(() -> "INSERT INTO legoset (id, version, name, manual) VALUES(:id, :number, :name, :number)") + .bind("id", lego.id) + .bind("name", lego.name) + .bind("number", lego.version) + .fetch().rowsUpdated() + .as(StepVerifier::create) + .expectNext(1L) + .verifyComplete(); + + databaseClient.sql("SELECT * FROM legoset") + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + } + + @Test // gh-34768 + void executeSelectWithReusedNamedParameterList() { + DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); + + String insertSql = "INSERT INTO legoset (id, version, name, manual) VALUES(:id, :version, :name, :manual)"; + String selectSql = "SELECT * FROM legoset WHERE version IN (:numbers) OR manual IN (:numbers)"; + Lego lego = new Lego(1, 42, "Star Wars", 99); + + databaseClient.sql(insertSql) + .bind("id", lego.id) + .bind("version", lego.version) + .bind("name", lego.name) + .bind("manual", lego.manual) + .fetch().rowsUpdated() + .as(StepVerifier::create) + .expectNext(1L) + .verifyComplete(); + + databaseClient.sql(selectSql) + // match version + .bind("numbers", List.of(2, 3, lego.version, 4)) + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + + databaseClient.sql(selectSql) + // match manual + .bind("numbers", List.of(2, 3, lego.manual, 4)) + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + } + + + record Lego(int id, Integer version, String name, Integer manual) { + } record ParameterRecord(int id, String name, Integer manual) { }