From 941b44fba61266aeb74cb0ec83285672413f5000 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Tue, 19 Feb 2019 17:31:40 +0100 Subject: [PATCH] Add DatabaseClient Coroutines extensions This commit introduces Coroutines support for `DatabaseClient` functional API via Kotlin extensions that provide suspendable functions prefixed by `await` for `Mono` based APIs. Extensions for `Flux` will be added when Kotlin/kotlinx.coroutines#254 will be fixed. It also provides `asType()` extensions useful for Reactive API as well. --- pom.xml | 34 ++++++ .../function/DatabaseClientExtensions.kt | 82 +++++++++++++ .../r2dbc/function/RowsFetchSpecExtensions.kt | 37 ++++++ .../function/DatabaseClientExtensionsTests.kt | 114 ++++++++++++++++++ .../function/RowsFetchSpecExtensionsTests.kt | 51 ++++++++ 5 files changed, 318 insertions(+) create mode 100644 src/main/kotlin/org/springframework/data/r2dbc/function/DatabaseClientExtensions.kt create mode 100644 src/main/kotlin/org/springframework/data/r2dbc/function/RowsFetchSpecExtensions.kt create mode 100644 src/test/kotlin/org/springframework/data/r2dbc/function/DatabaseClientExtensionsTests.kt create mode 100644 src/test/kotlin/org/springframework/data/r2dbc/function/RowsFetchSpecExtensionsTests.kt diff --git a/pom.xml b/pom.xml index 3891e1d5..0aa6dcd0 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,7 @@ Arabba-M7 1.0.1 1.10.1 + 1.1.1 @@ -126,6 +127,32 @@ reactor-core + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin} + true + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin} + true + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + ${coroutines.version} + true + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + ${coroutines.version} + true + + org.assertj assertj-core @@ -198,6 +225,13 @@ test + + io.mockk + mockk + 1.9.1 + test + + diff --git a/src/main/kotlin/org/springframework/data/r2dbc/function/DatabaseClientExtensions.kt b/src/main/kotlin/org/springframework/data/r2dbc/function/DatabaseClientExtensions.kt new file mode 100644 index 00000000..7799e7ca --- /dev/null +++ b/src/main/kotlin/org/springframework/data/r2dbc/function/DatabaseClientExtensions.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2018-2019 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 + * + * http://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.function + +import kotlinx.coroutines.reactive.awaitFirstOrNull + +/** + * Coroutines variant of [DatabaseClient.GenericExecuteSpec.then]. + * + * @author Sebastien Deleuze + */ +suspend fun DatabaseClient.GenericExecuteSpec.await() { + then().awaitFirstOrNull() +} + +/** + * Extension for [DatabaseClient.GenericExecuteSpec.as] providing a + * `asType()` variant. + * + * @author Sebastien Deleuze + */ +inline fun DatabaseClient.GenericExecuteSpec.asType(): DatabaseClient.TypedExecuteSpec + = `as`(T::class.java) + +/** + * Extension for [DatabaseClient.GenericSelectSpec.as] providing a + * `asType()` variant. + * + * @author Sebastien Deleuze + */ +inline fun DatabaseClient.GenericSelectSpec.asType(): DatabaseClient.TypedSelectSpec + = `as`(T::class.java) + +/** + * Coroutines variant of [DatabaseClient.TypedExecuteSpec.then]. + * + * @author Sebastien Deleuze + */ +suspend fun DatabaseClient.TypedExecuteSpec.await() { + then().awaitFirstOrNull() +} + +/** + * Extension for [DatabaseClient.TypedExecuteSpec.as] providing a + * `asType()` variant. + * + * @author Sebastien Deleuze + */ +inline fun DatabaseClient.TypedExecuteSpec.asType(): DatabaseClient.TypedExecuteSpec + = `as`(T::class.java) + +/** + * Coroutines variant of [DatabaseClient.InsertSpec.then]. + * + * @author Sebastien Deleuze + */ +suspend fun DatabaseClient.InsertSpec.await() { + then().awaitFirstOrNull() +} + +/** + * Extension for [DatabaseClient.InsertIntoSpec.into] providing a + * `into()` variant. + * + * @author Sebastien Deleuze + */ +inline fun DatabaseClient.InsertIntoSpec.into(): DatabaseClient.TypedInsertSpec + = into(T::class.java) + diff --git a/src/main/kotlin/org/springframework/data/r2dbc/function/RowsFetchSpecExtensions.kt b/src/main/kotlin/org/springframework/data/r2dbc/function/RowsFetchSpecExtensions.kt new file mode 100644 index 00000000..8b11402e --- /dev/null +++ b/src/main/kotlin/org/springframework/data/r2dbc/function/RowsFetchSpecExtensions.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2018-2019 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 + * + * http://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.function + +import kotlinx.coroutines.reactive.awaitFirstOrNull + +/** + * Coroutines variant of [RowsFetchSpec.one]. + * + * @author Sebastien Deleuze + */ +suspend fun RowsFetchSpec.awaitOne(): T? + = one().awaitFirstOrNull() + +/** + * Coroutines variant of [RowsFetchSpec.first]. + * + * @author Sebastien Deleuze + */ +suspend fun RowsFetchSpec.awaitFirst(): T? + = first().awaitFirstOrNull() + +// TODO Coroutines variant of [RowsFetchSpec.all], depends on [kotlinx.coroutines#254](https://github.com/Kotlin/kotlinx.coroutines/issues/254). +// suspend fun RowsFetchSpec.awaitAll() = all()... diff --git a/src/test/kotlin/org/springframework/data/r2dbc/function/DatabaseClientExtensionsTests.kt b/src/test/kotlin/org/springframework/data/r2dbc/function/DatabaseClientExtensionsTests.kt new file mode 100644 index 00000000..62dc461d --- /dev/null +++ b/src/test/kotlin/org/springframework/data/r2dbc/function/DatabaseClientExtensionsTests.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2018-2019 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 + * + * http://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.function + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test +import reactor.core.publisher.Mono + +class DatabaseClientExtensionsTests { + + @Test + fun genericExecuteSpecAwait() { + val spec = mockk() + every { spec.then() } returns Mono.empty() + runBlocking { + spec.await() + } + verify { + spec.then() + } + } + + @Test + fun genericExecuteSpecAsType() { + val genericSpec = mockk() + val typedSpec: DatabaseClient.TypedExecuteSpec = mockk() + every { genericSpec.`as`(String::class.java) } returns typedSpec + runBlocking { + assertEquals(typedSpec, genericSpec.asType()) + } + verify { + genericSpec.`as`(String::class.java) + } + } + + @Test + fun genericSelectSpecAsType() { + val genericSpec = mockk() + val typedSpec: DatabaseClient.TypedSelectSpec = mockk() + every { genericSpec.`as`(String::class.java) } returns typedSpec + runBlocking { + assertEquals(typedSpec, genericSpec.asType()) + } + verify { + genericSpec.`as`(String::class.java) + } + } + + @Test + fun typedExecuteSpecAwait() { + val spec = mockk>() + every { spec.then() } returns Mono.empty() + runBlocking { + spec.await() + } + verify { + spec.then() + } + } + + @Test + fun typedExecuteSpecAsType() { + val spec: DatabaseClient.TypedExecuteSpec = mockk() + every { spec.`as`(String::class.java) } returns spec + runBlocking { + assertEquals(spec, spec.asType()) + } + verify { + spec.`as`(String::class.java) + } + } + + @Test + fun insertSpecAwait() { + val spec = mockk>() + every { spec.then() } returns Mono.empty() + runBlocking { + spec.await() + } + verify { + spec.then() + } + } + + @Test + fun insertIntoSpecInto() { + val spec = mockk() + val typedSpec: DatabaseClient.TypedInsertSpec = mockk() + every { spec.into(String::class.java) } returns typedSpec + runBlocking { + assertEquals(typedSpec, spec.into()) + } + verify { + spec.into(String::class.java) + } + } +} diff --git a/src/test/kotlin/org/springframework/data/r2dbc/function/RowsFetchSpecExtensionsTests.kt b/src/test/kotlin/org/springframework/data/r2dbc/function/RowsFetchSpecExtensionsTests.kt new file mode 100644 index 00000000..760e9ef3 --- /dev/null +++ b/src/test/kotlin/org/springframework/data/r2dbc/function/RowsFetchSpecExtensionsTests.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2018-2019 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 + * + * http://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.function + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test +import reactor.core.publisher.Mono + +class RowsFetchSpecExtensionsTests { + + @Test + fun awaitOne() { + val spec = mockk>() + every { spec.one() } returns Mono.just("foo") + runBlocking { + assertEquals("foo", spec.awaitOne()) + } + verify { + spec.one() + } + } + + @Test + fun awaitFirst() { + val spec = mockk>() + every { spec.first() } returns Mono.just("foo") + runBlocking { + assertEquals("foo", spec.awaitFirst()) + } + verify { + spec.first() + } + } +}