From 5a5a5fec9eac8e3cfc42419b6946c47a8573d465 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 17 Feb 2020 17:38:47 +0100 Subject: [PATCH] #290 - Add Kotlin extensions for fluent R2dbcEntityTemplate API. --- .../core/ReactiveDeleteOperationExtensions.kt | 37 +++ .../core/ReactiveInsertOperationExtensions.kt | 37 +++ .../core/ReactiveSelectOperationExtensions.kt | 82 +++++++ .../core/ReactiveUpdateOperationExtensions.kt | 37 +++ .../springframework/data/r2dbc/core/Person.kt | 18 ++ ...ctiveDeleteOperationExtensionsUnitTests.kt | 56 +++++ ...ctiveInsertOperationExtensionsUnitTests.kt | 56 +++++ ...ctiveSelectOperationExtensionsUnitTests.kt | 220 ++++++++++++++++++ ...ctiveUpdateOperationExtensionsUnitTests.kt | 58 +++++ 9 files changed, 601 insertions(+) create mode 100644 src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveDeleteOperationExtensions.kt create mode 100644 src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveInsertOperationExtensions.kt create mode 100644 src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveSelectOperationExtensions.kt create mode 100644 src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveUpdateOperationExtensions.kt create mode 100644 src/test/kotlin/org/springframework/data/r2dbc/core/Person.kt create mode 100644 src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveDeleteOperationExtensionsUnitTests.kt create mode 100644 src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveInsertOperationExtensionsUnitTests.kt create mode 100644 src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveSelectOperationExtensionsUnitTests.kt create mode 100644 src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveUpdateOperationExtensionsUnitTests.kt diff --git a/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveDeleteOperationExtensions.kt b/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveDeleteOperationExtensions.kt new file mode 100644 index 00000000..4f592727 --- /dev/null +++ b/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveDeleteOperationExtensions.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2018-2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.r2dbc.core + +import kotlinx.coroutines.reactive.awaitSingle + +/** + * Extensions for [ReactiveDeleteOperation]. + * + * @author Mark Paluch + * @since 1.1 + */ + +/** + * Extension for [ReactiveDeleteOperation.delete] leveraging reified type parameters. + */ +inline fun ReactiveDeleteOperation.delete(): ReactiveDeleteOperation.ReactiveDelete = + delete(T::class.java) + +/** + * Coroutines variant of [ReactiveDeleteOperation.TerminatingDelete.all]. + */ +suspend fun ReactiveDeleteOperation.TerminatingDelete.allAndAwait(): Int = + all().awaitSingle() diff --git a/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveInsertOperationExtensions.kt b/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveInsertOperationExtensions.kt new file mode 100644 index 00000000..5dc96e49 --- /dev/null +++ b/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveInsertOperationExtensions.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.r2dbc.core + +import kotlinx.coroutines.reactive.awaitSingle + +/** + * Extensions for [ReactiveInsertOperation]. + * + * @author Mark Paluch + * @since 1.1 + */ + +/** + * Extension for [ReactiveInsertOperation.insert] leveraging reified type parameters. + */ +inline fun ReactiveInsertOperation.insert(): ReactiveInsertOperation.ReactiveInsert = + insert(T::class.java) + +/** + * Coroutines variant of [ReactiveInsertOperation.TerminatingInsert.using]. + */ +suspend inline fun ReactiveInsertOperation.TerminatingInsert.usingAndAwait(o: T): T = + using(o).awaitSingle() diff --git a/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveSelectOperationExtensions.kt b/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveSelectOperationExtensions.kt new file mode 100644 index 00000000..2087b942 --- /dev/null +++ b/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveSelectOperationExtensions.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.r2dbc.core + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle + +/** + * Extensions for [ReactiveSelectOperation]. + * + * @author Mark Paluch + * @since 1.1 + */ + +/** + * Extension for [ReactiveSelectOperation.select] leveraging reified type parameters. + */ +inline fun ReactiveSelectOperation.select(): ReactiveSelectOperation.ReactiveSelect = + select(T::class.java) + +/** + * Extension for [ReactiveSelectOperation.SelectWithProjection. as] leveraging reified type parameters. + */ +inline fun ReactiveSelectOperation.SelectWithProjection<*>.asType(): ReactiveSelectOperation.SelectWithQuery = + `as`(T::class.java) + +/** + * Non-nullable Coroutines variant of [ReactiveSelectOperation.TerminatingSelect.one]. + */ +suspend inline fun ReactiveSelectOperation.TerminatingSelect.awaitOne(): T = + one().awaitSingle() + +/** + * Nullable Coroutines variant of [ReactiveSelectOperation.TerminatingSelect.one]. + */ +suspend inline fun ReactiveSelectOperation.TerminatingSelect.awaitOneOrNull(): T? = + one().awaitFirstOrNull() + +/** + * Non-nullable Coroutines variant of [ReactiveSelectOperation.TerminatingSelect.first]. + */ +suspend inline fun ReactiveSelectOperation.TerminatingSelect.awaitFirst(): T = + first().awaitSingle() + +/** + * Nullable Coroutines variant of [ReactiveSelectOperation.TerminatingSelect.first]. + */ +suspend inline fun ReactiveSelectOperation.TerminatingSelect.awaitFirstOrNull(): T? = + first().awaitFirstOrNull() + +/** + * Coroutines variant of [ReactiveSelectOperation.TerminatingSelect.count]. + */ +suspend fun ReactiveSelectOperation.TerminatingSelect.awaitCount(): Long = + count().awaitSingle() + +/** + * Coroutines variant of [ReactiveSelectOperation.TerminatingSelect.exists]. + */ +suspend fun ReactiveSelectOperation.TerminatingSelect.awaitExists(): Boolean = + exists().awaitSingle() + +/** + * Coroutines [Flow] variant of [ReactiveSelectOperation.TerminatingSelect.all]. + */ +fun ReactiveSelectOperation.TerminatingSelect.flow(): Flow = + all().asFlow() diff --git a/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveUpdateOperationExtensions.kt b/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveUpdateOperationExtensions.kt new file mode 100644 index 00000000..6ed01ad0 --- /dev/null +++ b/src/main/kotlin/org/springframework/data/r2dbc/core/ReactiveUpdateOperationExtensions.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.r2dbc.core + +import kotlinx.coroutines.reactive.awaitSingle +import org.springframework.data.r2dbc.query.Update + +/** + * Extensions for [ReactiveUpdateOperation]. + * + * @author Mark Paluch + * @since 1.1 + */ + +/** + * Extension for [ReactiveUpdateOperation.update] leveraging reified type parameters. + */ +inline fun ReactiveUpdateOperation.update(): ReactiveUpdateOperation.ReactiveUpdate = + update(T::class.java) + +/** + * Coroutines variant of [ReactiveUpdateOperation.TerminatingUpdate.apply]. + */ +suspend fun ReactiveUpdateOperation.TerminatingUpdate.applyAndAwait(update: Update): Int = apply(update).awaitSingle() diff --git a/src/test/kotlin/org/springframework/data/r2dbc/core/Person.kt b/src/test/kotlin/org/springframework/data/r2dbc/core/Person.kt new file mode 100644 index 00000000..12f02107 --- /dev/null +++ b/src/test/kotlin/org/springframework/data/r2dbc/core/Person.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.r2dbc.core + +data class Person(val id: String) diff --git a/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveDeleteOperationExtensionsUnitTests.kt b/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveDeleteOperationExtensionsUnitTests.kt new file mode 100644 index 00000000..c89bdbf4 --- /dev/null +++ b/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveDeleteOperationExtensionsUnitTests.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.r2dbc.core + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import reactor.core.publisher.Mono + +/** + * Unit tests for [ReactiveDeleteOperationExtensions]. + * + * @author Mark Paluch + */ +class ReactiveDeleteOperationExtensionsUnitTests { + + val operations = mockk(relaxed = true) + + @Test // gh-290 + fun `delete() with reified type parameter extension should call its Java counterpart`() { + + operations.delete() + verify { operations.delete(Person::class.java) } + } + + @Test // gh-290 + fun allAndAwait() { + + val delete = mockk() + every { delete.all() } returns Mono.just(42) + + runBlocking { + assertThat(delete.allAndAwait()).isEqualTo(42) + } + + verify { + delete.all() + } + } +} diff --git a/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveInsertOperationExtensionsUnitTests.kt b/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveInsertOperationExtensionsUnitTests.kt new file mode 100644 index 00000000..2384b889 --- /dev/null +++ b/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveInsertOperationExtensionsUnitTests.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.r2dbc.core + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import reactor.core.publisher.Mono + +/** + * Unit tests for [ReactiveInsertOperationExtensions]. + * + * @author Mark Paluch + */ +class ReactiveInsertOperationExtensionsUnitTests { + + val operations = mockk(relaxed = true) + + @Test // gh-290 + fun `insert() with reified type parameter extension should call its Java counterpart`() { + + operations.insert() + verify { operations.insert(Person::class.java) } + } + + @Test // gh-290 + fun oneAndAwait() { + + val insert = mockk>() + every { insert.using("foo") } returns Mono.just("bar") + + runBlocking { + assertThat(insert.usingAndAwait("foo")).isEqualTo("bar") + } + + verify { + insert.using("foo") + } + } +} diff --git a/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveSelectOperationExtensionsUnitTests.kt b/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveSelectOperationExtensionsUnitTests.kt new file mode 100644 index 00000000..9faddc90 --- /dev/null +++ b/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveSelectOperationExtensionsUnitTests.kt @@ -0,0 +1,220 @@ +/* + * Copyright 2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.r2dbc.core + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatExceptionOfType +import org.junit.Test +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono + +/** + * Unit tests for [ReactiveSelectOperationExtensions]. + * + * @author Mark Paluch + */ +class ReactiveSelectOperationExtensionsUnitTests { + + val operations = mockk(relaxed = true) + + val operationWithProjection = mockk>(relaxed = true) + + @Test // gh-290 + fun `query() with reified type parameter extension should call its Java counterpart`() { + + operations.select() + verify { operations.select(Person::class.java) } + } + + @Test // gh-290 + fun `asType() with reified type parameter extension should call its Java counterpart`() { + + operationWithProjection.asType(); + verify { operationWithProjection.`as`(User::class.java) } + } + + @Test // gh-290 + fun terminatingFindAwaitOneWithValue() { + + val find = mockk>() + every { find.one() } returns Mono.just("foo") + + runBlocking { + assertThat(find.awaitOne()).isEqualTo("foo") + } + + verify { + find.one() + } + } + + @Test // gh-290 + fun terminatingFindAwaitOneWithNull() { + + val find = mockk>() + every { find.one() } returns Mono.empty() + + assertThatExceptionOfType(NoSuchElementException::class.java).isThrownBy { + runBlocking { find.awaitOne() } + } + + verify { + find.one() + } + } + + @Test // gh-290gh-290 + fun terminatingFindAwaitOneOrNullWithValue() { + + val find = mockk>() + every { find.one() } returns Mono.just("foo") + + runBlocking { + assertThat(find.awaitOneOrNull()).isEqualTo("foo") + } + + verify { + find.one() + } + } + + @Test // gh-290gh-290 + fun terminatingFindAwaitOneOrNullWithNull() { + + val find = mockk>() + every { find.one() } returns Mono.empty() + + runBlocking { + assertThat(find.awaitOneOrNull()).isNull() + } + + verify { + find.one() + } + } + + @Test // DATACASS-632 + fun terminatingFindAwaitFirstWithValue() { + + val find = mockk>() + every { find.first() } returns Mono.just("foo") + + runBlocking { + assertThat(find.awaitFirst()).isEqualTo("foo") + } + + verify { + find.first() + } + } + + @Test // gh-290gh-290 + fun terminatingFindAwaitFirstWithNull() { + + val find = mockk>() + every { find.first() } returns Mono.empty() + + assertThatExceptionOfType(NoSuchElementException::class.java).isThrownBy { + runBlocking { find.awaitFirst() } + } + + verify { + find.first() + } + } + + @Test // gh-290gh-290 + fun terminatingFindAwaitFirstOrNullWithValue() { + + val find = mockk>() + every { find.first() } returns Mono.just("foo") + + runBlocking { + assertThat(find.awaitFirstOrNull()).isEqualTo("foo") + } + + verify { + find.first() + } + } + + @Test // gh-290gh-290 + fun terminatingFindAwaitFirstOrNullWithNull() { + + val find = mockk>() + every { find.first() } returns Mono.empty() + + runBlocking { + assertThat(find.awaitFirstOrNull()).isNull() + } + + verify { + find.first() + } + } + + @Test // DATACASS-632 + fun terminatingFindAwaitCount() { + + val find = mockk>() + every { find.count() } returns Mono.just(1) + + runBlocking { + assertThat(find.awaitCount()).isEqualTo(1) + } + + verify { + find.count() + } + } + + @Test // DATACASS-632 + fun terminatingFindAwaitExists() { + + val find = mockk>() + every { find.exists() } returns Mono.just(true) + + runBlocking { + assertThat(find.awaitExists()).isTrue() + } + + verify { + find.exists() + } + } + + @Test // DATACASS-648 + fun terminatingFindAllAsFlow() { + + val spec = mockk>() + every { spec.all() } returns Flux.just("foo", "bar", "baz") + + runBlocking { + assertThat(spec.flow().toList()).contains("foo", "bar", "baz") + } + + verify { + spec.all() + } + } + + data class User(val id: String) +} diff --git a/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveUpdateOperationExtensionsUnitTests.kt b/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveUpdateOperationExtensionsUnitTests.kt new file mode 100644 index 00000000..75b9677b --- /dev/null +++ b/src/test/kotlin/org/springframework/data/r2dbc/core/ReactiveUpdateOperationExtensionsUnitTests.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.r2dbc.core + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.springframework.data.r2dbc.query.Update +import reactor.core.publisher.Mono + +/** + * Unit tests for [ReactiveUpdateOperationExtensions]. + * + * @author Mark Paluch + */ +class ReactiveUpdateOperationExtensionsUnitTests { + + val operations = mockk(relaxed = true) + + @Test// gh-290 + fun `update() with reified type parameter extension should call its Java counterpart`() { + + operations.update() + verify { operations.update(Person::class.java) } + } + + @Test // gh-290 + fun applyAndAwait() { + + val update = mockk() + val updateObj = mockk(); + every { update.apply(updateObj) } returns Mono.just(42) + + runBlocking { + assertThat(update.applyAndAwait(updateObj)).isEqualTo(42) + } + + verify { + update.apply(updateObj) + } + } +}