From 902e2d5a621b10f1de483e8f8ce5409220beb493 Mon Sep 17 00:00:00 2001 From: Stephen Cohen Date: Thu, 30 Jul 2020 18:43:45 -0400 Subject: [PATCH] #421 - Handle modifying query methods returning kotlin.Unit. Added check for kotlin.Unit to AbstractR2dbcQuery#getExecutionToWrap. This case is essentially equivalent to a return type of Void, but the singleton Unit instance needs to be returned instead of discarding the result entirely. It was also necessary to add a check to R2dbcQueryMethod#getEntityInformation, as otherwise a PersistentEntity is created for Unit, which leads to a new instance being created via reflection down the pipeline (which is probably not a thing that should happen). Original pull request: #422. --- .../reference/r2dbc-repositories.adoc | 2 +- .../repository/query/AbstractR2dbcQuery.java | 9 ++- .../repository/query/R2dbcQueryMethod.java | 7 +- ...stractR2dbcRepositoryIntegrationTests.java | 64 +++++++++++++++++++ .../query/R2dbcQueryMethodUnitTests.java | 13 ++++ 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/src/main/asciidoc/reference/r2dbc-repositories.adoc b/src/main/asciidoc/reference/r2dbc-repositories.adoc index 131fa0f..1e05c3b 100644 --- a/src/main/asciidoc/reference/r2dbc-repositories.adoc +++ b/src/main/asciidoc/reference/r2dbc-repositories.adoc @@ -267,7 +267,7 @@ Mono setFixedFirstnameFor(String firstname, String lastname); The result of a modifying query can be: -* `Void` to discard update count and await completion. +* `Void` (or Kotlin `Unit`) to discard update count and await completion. * `Integer` or another numeric type emitting the affected rows count. * `Boolean` to emit whether at least one row was updated. diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java index bae59f7..2181339 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java @@ -15,11 +15,13 @@ */ package org.springframework.data.r2dbc.repository.query; +import kotlin.Unit; + import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.reactivestreams.Publisher; - +import org.springframework.core.KotlinDetector; import org.springframework.data.mapping.model.EntityInstantiators; import org.springframework.data.r2dbc.convert.R2dbcConverter; import org.springframework.data.r2dbc.core.DatabaseClient; @@ -41,6 +43,7 @@ import org.springframework.util.Assert; * Base class for reactive {@link RepositoryQuery} implementations for R2DBC. * * @author Mark Paluch + * @author Stephen Cohen */ public abstract class AbstractR2dbcQuery implements RepositoryQuery { @@ -141,6 +144,10 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery { return (q, t, c) -> q.rowsUpdated().then(); } + if (KotlinDetector.isKotlinPresent() && Unit.class.isAssignableFrom(returnedType.getReturnedType())) { + return (q, t, c) -> q.rowsUpdated().thenReturn(Unit.INSTANCE); + } + return (q, t, c) -> q.rowsUpdated(); } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java index 0842c3b..19bf36a 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java @@ -17,9 +17,12 @@ package org.springframework.data.r2dbc.repository.query; import static org.springframework.data.repository.util.ClassUtils.*; +import kotlin.Unit; + import java.lang.reflect.Method; import java.util.Optional; +import org.springframework.core.KotlinDetector; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Page; @@ -51,6 +54,7 @@ import org.springframework.util.ClassUtils; * Reactive specific implementation of {@link QueryMethod}. * * @author Mark Paluch + * @author Stephen Cohen */ public class R2dbcQueryMethod extends QueryMethod { @@ -164,7 +168,8 @@ public class R2dbcQueryMethod extends QueryMethod { Class returnedObjectType = getReturnedObjectType(); Class domainClass = getDomainClass(); - if (ClassUtils.isPrimitiveOrWrapper(returnedObjectType)) { + if (ClassUtils.isPrimitiveOrWrapper(returnedObjectType) + || KotlinDetector.isKotlinPresent() && Unit.class.isAssignableFrom(returnedObjectType)) { this.metadata = new SimpleRelationalEntityMetadata<>((Class) domainClass, mappingContext.getRequiredPersistentEntity(domainClass)); diff --git a/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryIntegrationTests.java index 982847b..41a4afb 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryIntegrationTests.java @@ -17,6 +17,8 @@ package org.springframework.data.r2dbc.repository; import static org.assertj.core.api.Assertions.*; +import kotlin.Unit; + import io.r2dbc.spi.ConnectionFactory; import lombok.AllArgsConstructor; import lombok.Getter; @@ -297,6 +299,55 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg .verifyComplete(); } + @Test // gh-421 + public void shouldDeleteAllAndReturnCount() { + + shouldInsertNewItems(); + + repository.deleteAllAndReturnCount() // + .as(StepVerifier::create) // + .expectNext(2) // + .verifyComplete(); + + repository.findAll() // + .as(StepVerifier::create) // + .verifyComplete(); + } + + @Test // gh-421 + public void shouldDeleteAndReturnSuccess() { + + shouldInsertNewItems(); + + repository.deleteByManualAndReturnSuccess(12) // + .as(StepVerifier::create) // + .expectNext(true) // + .verifyComplete(); + + repository.findAll() // + .map(LegoSet::getManual) // + .as(StepVerifier::create) // + .expectNext(13) // + .verifyComplete(); + } + + @Test // gh-421 + public void shouldDeleteAndReturnKotlinUnit() { + + shouldInsertNewItems(); + + repository.deleteByManualAndReturnKotlinUnit(12) // + .as(StepVerifier::create) // + .expectNext(Unit.INSTANCE) // + .verifyComplete(); + + repository.findAll() // + .map(LegoSet::getManual) // + .as(StepVerifier::create) // + .expectNext(13) // + .verifyComplete(); + } + private Condition numberOf(int expected) { return new Condition<>(it -> { return it instanceof Number && ((Number) it).intValue() == expected; @@ -322,9 +373,22 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg Mono deleteAllBy(); + @Modifying @Query("DELETE from legoset where manual = :manual") Mono deleteAllByManual(int manual); + @Modifying + @Query("DELETE from legoset") + Mono deleteAllAndReturnCount(); + + @Modifying + @Query("DELETE from legoset where manual = :manual") + Mono deleteByManualAndReturnSuccess(int manual); + + @Modifying + @Query("DELETE from legoset where manual = :manual") + Mono deleteByManualAndReturnKotlinUnit(int manual); + Mono countByNameContains(String namePart); } diff --git a/src/test/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethodUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethodUnitTests.java index 26b07c4..aa3daa7 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethodUnitTests.java @@ -17,6 +17,8 @@ package org.springframework.data.r2dbc.repository.query; import static org.assertj.core.api.Assertions.*; +import kotlin.Unit; + import reactor.core.publisher.Mono; import java.lang.annotation.Retention; @@ -45,6 +47,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat * Unit test for {@link R2dbcQueryMethod}. * * @author Mark Paluch + * @author Stephen Cohen */ public class R2dbcQueryMethodUnitTests { @@ -128,6 +131,14 @@ public class R2dbcQueryMethodUnitTests { assertThat(method.getEntityInformation().getJavaType()).isAssignableFrom(Contact.class); } + @Test // gh-421 + public void fallsBackToRepositoryDomainTypeIfMethodReturnsKotlinUnit() throws Exception { + + R2dbcQueryMethod method = queryMethod(PersonRepository.class, "deleteByFirstname", String.class); + + assertThat(method.getEntityInformation().getJavaType()).isAssignableFrom(Contact.class); + } + private R2dbcQueryMethod queryMethod(Class repository, String name, Class... parameters) throws Exception { Method method = repository.getMethod(name, parameters); @@ -144,6 +155,8 @@ public class R2dbcQueryMethodUnitTests { Mono> findMonoSliceByLastname(String lastname, Pageable pageRequest); void deleteByUserName(String userName); + + Unit deleteByFirstname(String firstname); } interface SampleRepository extends Repository {