From 039001ad83a3d323bc92e0156ba52f42c578ec45 Mon Sep 17 00:00:00 2001 From: kihwankim Date: Sun, 9 Apr 2023 19:15:38 +0900 Subject: [PATCH 1/4] Add query util function in kotlin. --- .../data/r2dbc/core/query/QueryExtensions.kt | 10 +++ .../springframework/data/r2dbc/core/Person.kt | 10 ++- .../r2dbc/core/query/QueryExtensionsKtTest.kt | 79 +++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 spring-data-r2dbc/src/main/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensions.kt create mode 100644 spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensionsKtTest.kt diff --git a/spring-data-r2dbc/src/main/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensions.kt b/spring-data-r2dbc/src/main/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensions.kt new file mode 100644 index 0000000000..bf54bfb54d --- /dev/null +++ b/spring-data-r2dbc/src/main/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensions.kt @@ -0,0 +1,10 @@ +package org.springframework.data.r2dbc.core.query + +import org.springframework.data.relational.core.query.Criteria +import org.springframework.data.relational.core.query.Query + +fun query(vararg args: Criteria?): Query = Query.query( + args.fold(Criteria.empty()) { acc, arg -> + arg?.let { acc.and(it) } ?: acc + } +) diff --git a/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/Person.kt b/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/Person.kt index c55dd23bef..cf287611de 100644 --- a/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/Person.kt +++ b/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/Person.kt @@ -15,4 +15,12 @@ */ package org.springframework.data.r2dbc.core -data class Person(val id: String) +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table + +@Table("person") +data class Person( + @Id + val id: String, + val name: String, +) diff --git a/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensionsKtTest.kt b/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensionsKtTest.kt new file mode 100644 index 0000000000..4bbbcea45a --- /dev/null +++ b/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensionsKtTest.kt @@ -0,0 +1,79 @@ +package org.springframework.data.r2dbc.core.query + +import io.r2dbc.spi.R2dbcType +import io.r2dbc.spi.test.MockColumnMetadata +import io.r2dbc.spi.test.MockResult +import io.r2dbc.spi.test.MockRow +import io.r2dbc.spi.test.MockRowMetadata +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import org.springframework.data.r2dbc.core.* +import org.springframework.data.r2dbc.dialect.PostgresDialect +import org.springframework.data.r2dbc.testing.StatementRecorder +import org.springframework.data.relational.core.query.Criteria.where +import org.springframework.r2dbc.core.DatabaseClient +import reactor.test.StepVerifier + +/** + * Unit tests for [QueryExtensions]. + * + * @author kihwankim + */ +class QueryExtensionsKtTest { + private lateinit var client: DatabaseClient + private lateinit var entityTemplate: R2dbcEntityTemplate + private lateinit var recorder: StatementRecorder + + @BeforeEach + fun before() { + recorder = StatementRecorder.newInstance() + client = DatabaseClient.builder().connectionFactory(recorder) + .bindMarkers(PostgresDialect.INSTANCE.bindMarkersFactory).build() + entityTemplate = R2dbcEntityTemplate(client, DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE)) + } + + @ParameterizedTest // gh-1491 + @EnumSource(QueryConditionType::class) + fun shouldSelectAll(queryCondition: QueryConditionType) { + val metadata = MockRowMetadata.builder() + .columnMetadata(MockColumnMetadata.builder().name("id").type(R2dbcType.INTEGER).build()) + .columnMetadata(MockColumnMetadata.builder().name("name").type(R2dbcType.VARCHAR).build()) + .build() + val result = queryCondition.nameValue?.let { + listOf( + MockResult.builder() + .row( + MockRow.builder() + .identified("id", Any::class.java, 1L) + .identified("name", Any::class.java, queryCondition.nameValue) + .metadata(metadata) + .build() + ).build() + ) + } ?: emptyList() + + recorder.addStubbing({ s: String -> s.startsWith("SELECT") }, result) + + entityTemplate.select() + .matching( + query( + queryCondition.nameValue?.run { where("name").`is`(queryCondition.nameValue) } + ) + ) + .all() + .`as`(StepVerifier::create) + .expectNextCount(queryCondition.count) + .verifyComplete() + val statement = recorder.getCreatedStatement { s: String -> s.startsWith("SELECT") } + Assertions.assertThat(statement.sql) + .isEqualTo(queryCondition.resultSql) + } + + enum class QueryConditionType(val nameValue: String?, val count: Long, val resultSql: String) { + FILTER("testName", 1L, "SELECT person.* FROM person WHERE (person.name = $1)"), + NON_FILTER(null, 0L, "SELECT person.* FROM person") + } +} From acbc8141156a3c107dbfef9a9df3ee3d1faf4b99 Mon Sep 17 00:00:00 2001 From: kihwankim Date: Tue, 11 Apr 2023 22:17:16 +0900 Subject: [PATCH 2/4] Add column name fetch function --- .../data/r2dbc/core/R2dbcEntityTemplate.java | 24 +++++++++++++++++++ .../core/R2dbcEntityTemplateExtensions.kt | 9 +++++++ 2 files changed, 33 insertions(+) create mode 100644 spring-data-r2dbc/src/main/kotlin/org/springframework/data/r2dbc/core/R2dbcEntityTemplateExtensions.kt diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java index f88283fe5e..612d278584 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java @@ -18,17 +18,21 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.Row; import io.r2dbc.spi.RowMetadata; +import org.springframework.data.annotation.Transient; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.beans.FeatureDescriptor; +import java.lang.reflect.Field; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.reactivestreams.Publisher; import org.springframework.beans.BeansException; @@ -161,6 +165,26 @@ public R2dbcEntityTemplate(DatabaseClient databaseClient, ReactiveDataAccessStra this.projectionFactory = new SpelAwareProxyProjectionFactory(); } + public String getColumnName(Class tableType, Field field) { + Transient annotation = field.getDeclaredAnnotation(Transient.class); + if (annotation != null) { + throw new MappingException("This Column has Transient annotation"); + } + + org.springframework.data.relational.core.mapping.Column columnAnnotation = + field.getDeclaredAnnotation(org.springframework.data.relational.core.mapping.Column.class); + if (columnAnnotation != null) { + return columnAnnotation.value(); + } + + return StreamSupport.stream(Objects.requireNonNull(mappingContext.getPersistentEntity(tableType)).spliterator(), false) + .filter(column -> field.equals(column.getField())) + .findFirst() + .orElseThrow(() -> new MappingException("This Column name is not matched")) + .getColumnName() + .toString(); + } + @Override public DatabaseClient getDatabaseClient() { return this.databaseClient; diff --git a/spring-data-r2dbc/src/main/kotlin/org/springframework/data/r2dbc/core/R2dbcEntityTemplateExtensions.kt b/spring-data-r2dbc/src/main/kotlin/org/springframework/data/r2dbc/core/R2dbcEntityTemplateExtensions.kt new file mode 100644 index 0000000000..cb5ad5a1ff --- /dev/null +++ b/spring-data-r2dbc/src/main/kotlin/org/springframework/data/r2dbc/core/R2dbcEntityTemplateExtensions.kt @@ -0,0 +1,9 @@ +package org.springframework.data.r2dbc.core + +import org.springframework.data.mapping.MappingException +import kotlin.reflect.KProperty +import kotlin.reflect.jvm.javaField + +inline fun R2dbcEntityTemplate.column(property: KProperty): String = property.javaField?.let { + this.getColumnName(T::class.java, it) +} ?: throw MappingException("property is not valid") From f1aba7de4f38e309e2515236595604d511bdf9b9 Mon Sep 17 00:00:00 2001 From: kihwankim Date: Fri, 14 Apr 2023 20:38:54 +0900 Subject: [PATCH 3/4] Add query infix extensions --- .../core/query/CriteriaStepExtensions.kt | 51 +++++++++ .../core/query/CriteriaStepExtensionsTests.kt | 105 ++++++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/spring-data-relational/src/main/kotlin/org/springframework/data/relational/core/query/CriteriaStepExtensions.kt b/spring-data-relational/src/main/kotlin/org/springframework/data/relational/core/query/CriteriaStepExtensions.kt index df87dcd8ae..b6ae62be0a 100644 --- a/spring-data-relational/src/main/kotlin/org/springframework/data/relational/core/query/CriteriaStepExtensions.kt +++ b/spring-data-relational/src/main/kotlin/org/springframework/data/relational/core/query/CriteriaStepExtensions.kt @@ -25,6 +25,14 @@ package org.springframework.data.relational.core.query infix fun Criteria.CriteriaStep.isEqual(value: Any): Criteria = `is`(value) + +/** + * Extension for [Criteria.CriteriaStep.not] providing a + * `isNotEqual(value)` variant. + */ +infix fun Criteria.CriteriaStep.isNotEqual(value: Any): Criteria = + not(value) + /** * Extension for [Criteria.CriteriaStep.in] providing a * `isIn(value)` variant. @@ -44,3 +52,46 @@ fun Criteria.CriteriaStep.isIn(vararg value: Any): Criteria = */ fun Criteria.CriteriaStep.isIn(values: Collection): Criteria = `in`(values) + + +/** + * Extension for [Criteria.CriteriaStep.lessThan] providing a + * `le(value)` variant. + */ +infix fun Criteria.CriteriaStep.le(value: Any): Criteria = + lessThan(value) + +/** + * Extension for [Criteria.CriteriaStep.lessThanOrEquals] providing a + * `loe(value)` variant. + */ +infix fun Criteria.CriteriaStep.loe(value: Any): Criteria = + lessThanOrEquals(value) + +/** + * Extension for [Criteria.CriteriaStep.greaterThan] providing a + * `ge(value)` variant. + */ +infix fun Criteria.CriteriaStep.ge(value: Any): Criteria = + greaterThan(value) + +/** + * Extension for [Criteria.CriteriaStep.greaterThanOrEquals] providing a + * `goe(value)` variant. + */ +infix fun Criteria.CriteriaStep.goe(value: Any): Criteria = + greaterThanOrEquals(value) + +/** + * Extension for [Criteria.CriteriaStep.like] providing a + * `like(value)` variant. + */ +infix fun Criteria.CriteriaStep.like(value: Any): Criteria = + like(value) + +/** + * Extension for [Criteria.CriteriaStep.notLike] providing a + * `notLike(value)` variant. + */ +infix fun Criteria.CriteriaStep.notLike(value: Any): Criteria = + notLike(value) diff --git a/spring-data-relational/src/test/kotlin/org/springframework/data/relational/core/query/CriteriaStepExtensionsTests.kt b/spring-data-relational/src/test/kotlin/org/springframework/data/relational/core/query/CriteriaStepExtensionsTests.kt index 8c1a23ec8f..0c56988240 100644 --- a/spring-data-relational/src/test/kotlin/org/springframework/data/relational/core/query/CriteriaStepExtensionsTests.kt +++ b/spring-data-relational/src/test/kotlin/org/springframework/data/relational/core/query/CriteriaStepExtensionsTests.kt @@ -43,6 +43,21 @@ class CriteriaStepExtensionsTests { } } + @Test // gh-1491 + fun notEqIsCriteriaStep() { + + val spec = mockk() + val criteria = mockk() + + every { spec.not("test") } returns criteria + + assertThat(spec isNotEqual "test").isEqualTo(criteria) + + verify { + spec.not("test") + } + } + @Test // DATAJDBC-522 fun inVarargCriteriaStep() { @@ -72,4 +87,94 @@ class CriteriaStepExtensionsTests { spec.`in`(listOf("test")) } } + + @Test // gh-1491 + fun leCriteriaStep() { + + val spec = mockk() + val criteria = mockk() + + every { spec.lessThan(any()) } returns criteria + + assertThat(spec le 10).isEqualTo(criteria) + + verify { + spec.lessThan(10) + } + } + + @Test // gh-1491 + fun loeCriteriaStep() { + + val spec = mockk() + val criteria = mockk() + + every { spec.lessThanOrEquals(10) } returns criteria + + assertThat(spec loe 10).isEqualTo(criteria) + + verify { + spec.lessThanOrEquals(10) + } + } + + @Test + fun geCriteriaStep() { + + val spec = mockk() + val criteria = mockk() + + every { spec.greaterThan(10) } returns criteria + + assertThat(spec ge 10).isEqualTo(criteria) + + verify { + spec.greaterThan(10) + } + } + + @Test + fun goeCriteriaStep() { + + val spec = mockk() + val criteria = mockk() + + every { spec.greaterThanOrEquals(10) } returns criteria + + assertThat(spec goe 10).isEqualTo(criteria) + + verify { + spec.greaterThanOrEquals(10) + } + } + + @Test + fun likeCriteriaStep() { + + val spec = mockk() + val criteria = mockk() + + every { spec.like("abc%") } returns criteria + + assertThat(spec like "abc%").isEqualTo(criteria) + + verify { + spec.like("abc%") + } + } + + @Test + fun notLikeCriteriaStep() { + + val spec = mockk() + val criteria = mockk() + + every { spec.notLike("abc%") } returns criteria + + assertThat(spec notLike "abc%").isEqualTo(criteria) + + verify { + spec.notLike("abc%") + } + } } From 7ead63a5d438033480c9de5016381990504ac23e Mon Sep 17 00:00:00 2001 From: kihwankim Date: Fri, 14 Apr 2023 20:44:08 +0900 Subject: [PATCH 4/4] Change to kotiln criteria extension infix function --- .../data/r2dbc/core/query/QueryExtensionsKtTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensionsKtTest.kt b/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensionsKtTest.kt index 4bbbcea45a..8a71cca6f9 100644 --- a/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensionsKtTest.kt +++ b/spring-data-r2dbc/src/test/kotlin/org/springframework/data/r2dbc/core/query/QueryExtensionsKtTest.kt @@ -14,6 +14,7 @@ import org.springframework.data.r2dbc.core.* import org.springframework.data.r2dbc.dialect.PostgresDialect import org.springframework.data.r2dbc.testing.StatementRecorder import org.springframework.data.relational.core.query.Criteria.where +import org.springframework.data.relational.core.query.isEqual import org.springframework.r2dbc.core.DatabaseClient import reactor.test.StepVerifier @@ -60,7 +61,7 @@ class QueryExtensionsKtTest { entityTemplate.select() .matching( query( - queryCondition.nameValue?.run { where("name").`is`(queryCondition.nameValue) } + queryCondition.nameValue?.run { where("name") isEqual queryCondition.nameValue } ) ) .all()