From 2f60083cd53abf959b0b47199478464721d3cbb8 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 22 Apr 2025 12:45:35 +0200 Subject: [PATCH] Add integration tests for reused named parameters from bean properties See gh-34768 --- .../simple/JdbcClientIntegrationTests.java | 91 ++++++--- ...bstractDatabaseClientIntegrationTests.java | 184 ++++++++++++------ 2 files changed, 186 insertions(+), 89 deletions(-) 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 a78e31a1f8..84c2f7981f 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 @@ -20,6 +20,7 @@ import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassRelativeResourceLoader; @@ -145,14 +146,11 @@ 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 = """ + @Nested // gh-34768 + class ReusedNamedParameterTests { + + private static final String QUERY1 = """ select * from users where first_name in ('Bogus', :name) or @@ -160,22 +158,7 @@ class JdbcClientIntegrationTests { 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 = """ + private static final String QUERY2 = """ select * from users where first_name in (:names) or @@ -183,12 +166,64 @@ class JdbcClientIntegrationTests { 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")); + @BeforeEach + void insertTestUsers() { + jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "John").update(); + jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "Smith").update(); + jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("Smith", "Smith").update(); + assertNumUsers(4); + } + + @Test + void selectWithReusedNamedParameter() { + List users = jdbcClient.sql(QUERY1) + .param("name", "John") + .query(User.class) + .list(); + + assertResults(users); + } + + @Test + void selectWithReusedNamedParameterFromBeanProperties() { + List users = jdbcClient.sql(QUERY1) + .paramSource(new Name("John")) + .query(User.class) + .list(); + + assertResults(users); + } + + @Test + void selectWithReusedNamedParameterList() { + List users = jdbcClient.sql(QUERY2) + .param("names", List.of("John", "Bogus")) + .query(User.class) + .list(); + + assertResults(users); + } + + @Test + void selectWithReusedNamedParameterListFromBeanProperties() { + List users = jdbcClient.sql(QUERY2) + .paramSource(new Names(List.of("John", "Bogus"))) + .query(User.class) + .list(); + + assertResults(users); + } + + + private static void assertResults(List users) { + assertThat(users).containsExactly(new User(2, "John", "John"), new User(3, "John", "Smith")); + } + + record Name(String name) {} + + record Names(List names) {} + } 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 b6a91fbcf7..c992aa242f 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 @@ -23,6 +23,7 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.Parameters; import io.r2dbc.spi.Result; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Mark Paluch * @author Mingyuan Wu * @author Juergen Hoeller + * @author Sam Brannen */ abstract class AbstractDatabaseClientIntegrationTests { @@ -121,7 +123,8 @@ abstract class AbstractDatabaseClientIntegrationTests { DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); databaseClient.sql("INSERT INTO legoset (id, name, manual) VALUES(:id, :name, :manual)") - .bindValues(Map.of("id", 42055, + .bindValues(Map.of( + "id", 42055, "name", Parameters.in("SCHAUFELRADBAGGER"), "manual", Parameters.in(Integer.class))) .fetch().rowsUpdated() @@ -199,8 +202,7 @@ abstract class AbstractDatabaseClientIntegrationTests { void shouldEmitGeneratedKey() { DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); - databaseClient.sql( - "INSERT INTO legoset ( name, manual) VALUES(:name, :manual)") + databaseClient.sql("INSERT INTO legoset ( name, manual) VALUES(:name, :manual)") .bind("name","SCHAUFELRADBAGGER") .bindNull("manual", Integer.class) .filter(statement -> statement.returnGeneratedValues("id")) @@ -211,69 +213,129 @@ abstract class AbstractDatabaseClientIntegrationTests { .verifyComplete(); } - @Test // gh-34768 - void executeInsertWithReusedNamedParameter() { - DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); - Lego lego = new Lego(1, 42, "Star Wars", 42); + @Nested + class ReusedNamedParameterTests { - 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(); + @Test // gh-34768 + void executeInsertWithReusedNamedParameter() { + DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); + + Lego lego = new Lego(1, 42, "Star Wars", 42); + + // ":number" is reused. + 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)"; + // ":numbers" is reused. + 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(); + } + + @Test // gh-34768 + void executeSelectWithReusedNamedParameterListFromBeanProperties() { + DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); + + String insertSql = "INSERT INTO legoset (id, version, name, manual) VALUES(:id, :version, :name, :manual)"; + // ":numbers" is reused. + 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 + .bindProperties(new LegoRequest(List.of(lego.version))) + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + + databaseClient.sql(selectSql) + // match manual + .bindProperties(new LegoRequest(List.of(lego.manual))) + .mapProperties(Lego.class) + .first() + .as(StepVerifier::create) + .assertNext(actual -> assertThat(actual).isEqualTo(lego)) + .verifyComplete(); + } + + + record Lego(int id, Integer version, String name, Integer manual) { + } + + static class LegoRequest { + + private final List numbers; + + LegoRequest(List numbers) { + this.numbers = numbers; + } + + public List getNumbers() { + return numbers; + } + } - 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) { }