DATAREDIS-1033 - Add Kotlin Flow based extensions.

Original Pull Request: #477
This commit is contained in:
Sebastien Deleuze
2019-09-10 06:26:30 +02:00
committed by Christoph Strobl
parent 669d3a9c5c
commit 78b895feb4
16 changed files with 1541 additions and 22 deletions

View File

@@ -15,12 +15,19 @@
*/
package org.springframework.data.redis.core
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.reactive.awaitSingle
import kotlinx.coroutines.reactor.asFlux
import org.springframework.data.geo.Circle
import org.springframework.data.geo.Distance
import org.springframework.data.geo.GeoResult
import org.springframework.data.geo.Metric
import org.springframework.data.geo.Point
import org.springframework.data.redis.connection.RedisGeoCommands
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation
import org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs
/**
* Coroutines variant of [ReactiveGeoOperations.add].
@@ -37,7 +44,7 @@ suspend fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.addAndAwait(key: K, p
* @author Mark Paluch
* @since 2.2
*/
suspend fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.addAndAwait(key: K, location: RedisGeoCommands.GeoLocation<M>): Long =
suspend fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.addAndAwait(key: K, location: GeoLocation<M>): Long =
add(key, location).awaitSingle()
/**
@@ -55,9 +62,19 @@ suspend fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.addAndAwait(key: K, m
* @author Mark Paluch
* @since 2.2
*/
suspend fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.addAndAwait(key: K, locations: Iterable<RedisGeoCommands.GeoLocation<M>>): Long =
suspend fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.addAndAwait(key: K, locations: Iterable<GeoLocation<M>>): Long =
add(key, locations).awaitSingle()
/**
* Coroutines [Flow] variant of [ReactiveGeoOperations.add].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.add(key: K, locations: Flow<Collection<GeoLocation<M>>>): Flow<Long> =
add(key, locations.asFlux()).asFlow()
/**
* Coroutines variant of [ReactiveGeoOperations.distance].
*
@@ -115,6 +132,37 @@ suspend fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.positionAndAwait(key:
suspend fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.positionAndAwait(key: K, vararg members: M): List<Point> =
position(key, *members).awaitSingle()
/**
* Coroutines [Flow] variant of [ReactiveGeoOperations.radius].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.radiusAsFlow(key: K, within: Circle, args: GeoRadiusCommandArgs? = null): Flow<GeoResult<GeoLocation<M>>> =
(if (args != null) radius(key, within, args) else radius(key, within)).asFlow()
/**
* Coroutines [Flow] variant of [ReactiveGeoOperations.radius].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.radiusAsFlow(key: K, member: M, radius: Double): Flow<GeoResult<GeoLocation<M>>> =
radius(key, member, radius).asFlow()
/**
* Coroutines [Flow] variant of [ReactiveGeoOperations.radius].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, M : Any> ReactiveGeoOperations<K, M>.radiusAsFlow(key: K, member: M, distance: Distance, args: GeoRadiusCommandArgs? = null): Flow<GeoResult<GeoLocation<M>>> =
(if (args != null) radius(key, member, distance, args) else radius(key, member, distance)).asFlow()
/**
* Coroutines variant of [ReactiveGeoOperations.remove].
*

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.data.redis.core
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.reactive.awaitSingle
@@ -55,6 +58,16 @@ suspend fun <H : Any, HK : Any, HV : Any> ReactiveHashOperations<H, HK, HV>.mult
suspend fun <H : Any, HK : Any, HV : Any> ReactiveHashOperations<H, HK, HV>.incrementAndAwait(key: H, hashKey: HK, delta: Long): Long =
increment(key, hashKey, delta).awaitSingle()
/**
* Coroutines variant of [ReactiveHashOperations.keys].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <H : Any, HK : Any, HV : Any> ReactiveHashOperations<H, HK, HV>.keysAsFlow(key: H): Flow<HK> =
keys(key).asFlow()
/**
* Coroutines variant of [ReactiveHashOperations.increment].
*
@@ -100,6 +113,36 @@ suspend fun <H : Any, HK : Any, HV : Any> ReactiveHashOperations<H, HK, HV>.putA
suspend fun <H : Any, HK : Any, HV : Any> ReactiveHashOperations<H, HK, HV>.putIfAbsentAndAwait(key: H, hashKey: HK, hashValue: HV): Boolean =
putIfAbsent(key, hashKey, hashValue).awaitSingle()
/**
* Coroutines variant of [ReactiveHashOperations.values].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <H : Any, HK : Any, HV : Any> ReactiveHashOperations<H, HK, HV>.valuesAsFlow(key: H): Flow<HV> =
values(key).asFlow()
/**
* Coroutines variant of [ReactiveHashOperations.entries].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <H : Any, HK : Any, HV : Any> ReactiveHashOperations<H, HK, HV>.entriesAsFlow(key: H): Flow<Map.Entry<HK, HV>> =
entries(key).asFlow()
/**
* Coroutines variant of [ReactiveHashOperations.scan].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <H : Any, HK : Any, HV : Any> ReactiveHashOperations<H, HK, HV>.scanAsFlow(key: H, options: ScanOptions = ScanOptions.NONE): Flow<Map.Entry<HK, HV>> =
scan(key, options).asFlow()
/**
* Coroutines variant of [ReactiveHashOperations.remove].
*

View File

@@ -15,10 +15,23 @@
*/
package org.springframework.data.redis.core
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.reactive.awaitSingle
import java.time.Duration
/**
* Coroutines variant of [ReactiveListOperations.range].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveListOperations<K, V>.rangeAsFlow(key: K, start: Long, end: Long): Flow<V> =
range(key, start, end).asFlow()
/**
* Coroutines variant of [ReactiveListOperations.trim].
*

View File

@@ -15,12 +15,52 @@
*/
package org.springframework.data.redis.core
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactive.asPublisher
import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.reactive.awaitSingle
import org.springframework.data.redis.connection.DataType
import org.springframework.data.redis.connection.ReactiveRedisConnection
import org.springframework.data.redis.connection.ReactiveSubscription.*
import org.springframework.data.redis.core.script.RedisScript
import org.springframework.data.redis.listener.Topic
import org.springframework.data.redis.serializer.RedisElementReader
import org.springframework.data.redis.serializer.RedisElementWriter
import java.time.Duration
import java.time.Instant
/**
* Coroutines variant of [ReactiveRedisOperations.execute].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any, T : Any> ReactiveRedisOperations<K, V>.executeAsFlow(action: (ReactiveRedisConnection) -> Flow<T>): Flow<T> =
execute { action(it).asPublisher() }.asFlow()
/**
* Coroutines variant of [ReactiveRedisOperations.execute].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any, T : Any> ReactiveRedisOperations<K, V>.executeAsFlow(script: RedisScript<T>, keys: List<K> = emptyList(), args: List<*> = emptyList<Any>()): Flow<T> =
execute(script, keys, args).asFlow()
/**
* Coroutines variant of [ReactiveRedisOperations.execute].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any, T : Any> ReactiveRedisOperations<K, V>.executeAsFlow(script: RedisScript<T>, keys: List<K> = emptyList(), args: List<*> = emptyList<Any>(), argsWriter: RedisElementWriter<*>, resultReader: RedisElementReader<T>): Flow<T> =
execute(script, keys, args, argsWriter, resultReader).asFlow()
/**
* Coroutines variant of [ReactiveRedisOperations.convertAndSend].
*
@@ -30,6 +70,36 @@ import java.time.Instant
suspend fun <K : Any, V : Any> ReactiveRedisOperations<K, V>.sendAndAwait(destination: String, message: V): Long =
convertAndSend(destination, message).awaitSingle()
/**
* Coroutines variant of [ReactiveRedisOperations.listenToChannel].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveRedisOperations<K, V>.listenToChannelAsFlow(vararg channels: String): Flow<Message<String, V>> =
listenToChannel(*channels).asFlow()
/**
* Coroutines variant of [ReactiveRedisOperations.listenToPattern].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveRedisOperations<K, V>.listenToPatternAsFlow(vararg patterns: String): Flow<Message<String, V>> =
listenToPattern(*patterns).asFlow()
/**
* Coroutines variant of [ReactiveRedisOperations.listenTo].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveRedisOperations<K, V>.listenToAsFlow(vararg topics: Topic): Flow<Message<String, V>> =
listenTo(*topics).asFlow()
/**
* Coroutines variant of [ReactiveRedisOperations.hasKey].
*
@@ -48,6 +118,26 @@ suspend fun <K : Any, V : Any> ReactiveRedisOperations<K, V>.hasKeyAndAwait(key:
suspend fun <K : Any, V : Any> ReactiveRedisOperations<K, V>.typeAndAwait(key: K): DataType =
type(key).awaitSingle()
/**
* Coroutines variant of [ReactiveRedisOperations.keys].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveRedisOperations<K, V>.keysAsFlow(pattern: K): Flow<K> =
keys(pattern).asFlow()
/**
* Coroutines variant of [ReactiveRedisOperations.scan].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveRedisOperations<K, V>.scanAsFlow(options: ScanOptions = ScanOptions.NONE): Flow<K> =
scan(options).asFlow()
/**
* Coroutines variant of [ReactiveRedisOperations.randomKey].
*

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.data.redis.core
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.reactive.awaitSingle
@@ -45,6 +48,16 @@ suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.removeAndAwait(key: K
suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.popAndAwait(key: K): V? =
pop(key).awaitFirstOrNull()
/**
* Coroutines variant of [ReactiveSetOperations.pop].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.popAsFlow(key: K, count: Long): Flow<V> =
pop(key, count).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.move].
*
@@ -72,6 +85,36 @@ suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.sizeAndAwait(key: K):
suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.isMemberAndAwait(key: K, value: V): Boolean =
isMember(key, value).awaitSingle()
/**
* Coroutines variant of [ReactiveSetOperations.intersect].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.intersectAsFlow(key: K, otherKey: K): Flow<V> =
intersect(key, otherKey).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.intersect].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.intersectAsFlow(key: K, otherKeys: Collection<K>): Flow<V> =
intersect(key, otherKeys).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.intersect].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.intersectAsFlow(otherKeys: Collection<K>): Flow<V> =
intersect(otherKeys).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.intersectAndStore].
*
@@ -90,6 +133,36 @@ suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.intersectAndStoreAndA
suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.intersectAndStoreAndAwait(keys: Collection<K>, destKey: K): Long =
intersectAndStore(keys, destKey).awaitSingle()
/**
* Coroutines variant of [ReactiveSetOperations.union].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.unionAsFlow(key: K, otherKey: K): Flow<V> =
union(key, otherKey).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.union].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.unionAsFlow(key: K, otherKeys: Collection<K>): Flow<V> =
union(key, otherKeys).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.union].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.unionAsFlow(otherKeys: Collection<K>): Flow<V> =
union(otherKeys).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.unionAndStore].
*
@@ -108,6 +181,36 @@ suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.unionAndStoreAndAwait
suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.unionAndStoreAndAwait(keys: Collection<K>, destKey: K): Long =
unionAndStore(keys, destKey).awaitSingle()
/**
* Coroutines variant of [ReactiveSetOperations.difference].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.differenceAsFlow(key: K, otherKey: K): Flow<V> =
difference(key, otherKey).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.difference].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.differenceAsFlow(key: K, otherKeys: Collection<K>): Flow<V> =
difference(key, otherKeys).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.difference].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.differenceAsFlow(otherKeys: Collection<K>): Flow<V> =
difference(otherKeys).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.differenceAndStore].
*
@@ -126,6 +229,25 @@ suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.differenceAndStoreAnd
suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.differenceAndStoreAndAwait(keys: Collection<K>, destKey: K): Long =
differenceAndStore(keys, destKey).awaitSingle()
/**
* Coroutines variant of [ReactiveSetOperations.members].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.membersAsFlow(key: K): Flow<V> =
members(key).asFlow()
/**
* Coroutines variant of [ReactiveHashOperations.scan].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.scanAsFlow(key: K, options: ScanOptions = ScanOptions.NONE): Flow<V> =
scan(key, options).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.randomMember].
*
@@ -135,6 +257,26 @@ suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.differenceAndStoreAnd
suspend fun <K : Any, V : Any> ReactiveSetOperations<K, V>.randomMemberAndAwait(key: K): V? =
randomMember(key).awaitFirstOrNull()
/**
* Coroutines variant of [ReactiveSetOperations.distinctRandomMembers].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.distinctRandomMembersAsFlow(key: K, count: Long): Flow<V> =
distinctRandomMembers(key, count).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.randomMembers].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveSetOperations<K, V>.randomMembersAsFlow(key: K, count: Long): Flow<V> =
randomMembers(key, count).asFlow()
/**
* Coroutines variant of [ReactiveSetOperations.delete].
*

View File

@@ -15,7 +15,13 @@
*/
package org.springframework.data.redis.core
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactive.asPublisher
import kotlinx.coroutines.reactive.awaitSingle
import org.springframework.data.domain.Range
import org.springframework.data.redis.connection.RedisZSetCommands.*
import org.springframework.data.redis.connection.stream.*
/**
@@ -45,6 +51,16 @@ suspend fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.ac
suspend fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.acknowledgeAndAwait(group: String, record: Record<K, *>): Long =
acknowledge(group, record).awaitSingle()
/**
* Coroutines variant of [ReactiveStreamOperations.add].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.add(key: K, bodyFlow: Flow<Map<HK, HV>>): Flow<RecordId> =
add(key, bodyFlow.asPublisher()).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.add].
*
@@ -135,6 +151,128 @@ suspend fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.de
suspend fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.sizeAndAwait(key: K): Long =
size(key).awaitSingle()
/**
* Coroutines variant of [ReactiveStreamOperations.range].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.rangeAsFlow(key: K, range: Range<String>, limit: Limit = Limit.unlimited()): Flow<MapRecord<K, HK, HV>>
= range(key, range, limit).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.range].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
inline fun <K : Any, reified V : Any> ReactiveStreamOperations<K, *, *>.rangeWithTypeAsFlow(key: K, range: Range<String>, limit: Limit = Limit.unlimited()): Flow<ObjectRecord<K, V>>
= range(V::class.java, key, range, limit).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.read].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.readAsFlow(vararg stream: StreamOffset<K>): Flow<MapRecord<K, HK, HV>> =
read(*stream).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.read].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.readAsFlow(readOptions: StreamReadOptions, vararg stream: StreamOffset<K>): Flow<MapRecord<K, HK, HV>> =
read(readOptions, *stream).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.read].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
inline fun <K : Any, reified V : Any> ReactiveStreamOperations<K, *, *>.readWithTypeAsFlow(vararg stream: StreamOffset<K>): Flow<ObjectRecord<K, V>> =
read(V::class.java, *stream).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.read].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
inline fun <K : Any, reified V : Any> ReactiveStreamOperations<K, *, *>.readWithTypeAsFlow(readOptions: StreamReadOptions, vararg stream: StreamOffset<K>): Flow<ObjectRecord<K, V>> =
read(V::class.java, readOptions, *stream).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.read].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.readAsFlow(consumer: Consumer, vararg stream: StreamOffset<K>): Flow<MapRecord<K, HK, HV>> =
read(consumer, *stream).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.read].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.readAsFlow(consumer: Consumer, readOptions: StreamReadOptions, vararg stream: StreamOffset<K>): Flow<MapRecord<K, HK, HV>> =
read(consumer, readOptions, *stream).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.read].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
inline fun <K : Any, reified V : Any> ReactiveStreamOperations<K, *, *>.readWithTypeAsFlow(consumer: Consumer, vararg stream: StreamOffset<K>): Flow<ObjectRecord<K, V>> =
read(V::class.java, consumer, *stream).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.read].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
inline fun <K : Any, reified V : Any> ReactiveStreamOperations<K, *, *>.readWithTypeAsFlow(consumer: Consumer, readOptions: StreamReadOptions, vararg stream: StreamOffset<K>): Flow<ObjectRecord<K, V>> =
read(V::class.java, consumer, readOptions, *stream).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.reverseRange].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, HK : Any, HV : Any> ReactiveStreamOperations<K, HK, HV>.reverseRangeAsFlow(key: K, range: Range<String>, limit: Limit = Limit.unlimited()): Flow<MapRecord<K, HK, HV>>
= reverseRange(key, range, limit).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.reverseRange].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
inline fun <K : Any, reified V : Any> ReactiveStreamOperations<K, *, *>.reverseRangeWithTypeAsFlow(key: K, range: Range<String>, limit: Limit = Limit.unlimited()): Flow<ObjectRecord<K, V>> =
reverseRange(V::class.java, key, range, limit).asFlow()
/**
* Coroutines variant of [ReactiveStreamOperations.trim].
*

View File

@@ -15,10 +15,15 @@
*/
package org.springframework.data.redis.core
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.reactive.awaitSingle
import org.springframework.data.domain.Range
import org.springframework.data.redis.connection.RedisZSetCommands
import org.springframework.data.redis.connection.RedisZSetCommands.Limit
import org.springframework.data.redis.core.ZSetOperations.*
/**
* Coroutines variant of [ReactiveZSetOperations.add].
@@ -35,7 +40,7 @@ suspend fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.addAndAwait(key: K,
* @author Mark Paluch
* @since 2.2
*/
suspend fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.addAllAndAwait(key: K, values: Collection<ZSetOperations.TypedTuple<V>>): Long =
suspend fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.addAllAndAwait(key: K, values: Collection<TypedTuple<V>>): Long =
addAll(key, values).awaitSingle()
/**
@@ -74,6 +79,86 @@ suspend fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.rankAndAwait(key: K,
suspend fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.reverseRankAndAwait(key: K, value: V): Long? =
reverseRank(key, value).awaitFirstOrNull()
/**
* Coroutines variant of [ReactiveZSetOperations.range].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.rangeAsFlow(key: K, range: Range<Long>): Flow<V> =
range(key, range).asFlow()
/**
* Coroutines variant of [ReactiveZSetOperations.rangeWithScores].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.rangeWithScoresAsFlow(key: K, range: Range<Long>): Flow<TypedTuple<V>> =
rangeWithScores(key, range).asFlow()
/**
* Coroutines variant of [ReactiveZSetOperations.rangeByScore].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.rangeByScoreAsFlow(key: K, range: Range<Double>, limit: Limit? = null): Flow<V> =
(if (limit == null) rangeByScore(key, range) else rangeByScore(key, range, limit)).asFlow()
/**
* Coroutines variant of [ReactiveZSetOperations.rangeByScoreWithScores].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.rangeByScoreWithScoresAsFlow(key: K, range: Range<Double>, limit: Limit? = null): Flow<TypedTuple<V>> =
(if (limit == null) rangeByScoreWithScores(key, range) else rangeByScoreWithScores(key, range, limit)).asFlow()
/**
* Coroutines variant of [ReactiveZSetOperations.reverseRange].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.reverseRangeAsFlow(key: K, range: Range<Long>): Flow<V> =
reverseRange(key, range).asFlow()
/**
* Coroutines variant of [ReactiveZSetOperations.reverseRangeWithScores].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.reverseRangeWithScoresAsFlow(key: K, range: Range<Long>): Flow<TypedTuple<V>> =
reverseRangeWithScores(key, range).asFlow()
/**
* Coroutines variant of [ReactiveZSetOperations.reverseRangeByScore].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.reverseRangeByScoreAsFlow(key: K, range: Range<Double>, limit: Limit? = null): Flow<V> =
(if (limit == null) reverseRangeByScore(key, range) else reverseRangeByScore(key, range, limit)).asFlow()
/**
* Coroutines variant of [ReactiveZSetOperations.reverseRangeByScoreWithScores].
*
* @author Sebastien Deleuze
* @since 2.2
*/
@ExperimentalCoroutinesApi
fun <K : Any, V : Any> ReactiveZSetOperations<K, V>.reverseRangeByScoreWithScoresAsFlow(key: K, range: Range<Double>, limit: Limit? = null): Flow<TypedTuple<V>> =
(if (limit == null) reverseRangeByScoreWithScores(key, range) else reverseRangeByScoreWithScores(key, range, limit)).asFlow()
/**
* Coroutines variant of [ReactiveZSetOperations.count].
*

View File

@@ -18,17 +18,26 @@ package org.springframework.data.redis.core
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.reactivestreams.Publisher
import org.springframework.data.geo.Circle
import org.springframework.data.geo.Distance
import org.springframework.data.geo.GeoResult
import org.springframework.data.geo.Metrics
import org.springframework.data.geo.Point
import org.springframework.data.redis.connection.RedisGeoCommands
import org.springframework.data.redis.connection.RedisGeoCommands.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
/**
* Unit tests for [ReactiveGeoOperationsExtensions].
* Unit tests for `ReactiveGeoOperationsExtensions`.
*
* @author Mark Paluch
* @author Christoph Strobl
@@ -54,14 +63,14 @@ class ReactiveGeoOperationsExtensionsUnitTests {
fun addGeoLocation() {
val operations = mockk<ReactiveGeoOperations<String, String>>()
every { operations.add(any(), any<RedisGeoCommands.GeoLocation<String>>()) } returns Mono.just(1)
every { operations.add(any(), any<GeoLocation<String>>()) } returns Mono.just(1)
runBlocking {
assertThat(operations.addAndAwait("foo", RedisGeoCommands.GeoLocation("bar", Point(1.0, 2.0)))).isEqualTo(1)
assertThat(operations.addAndAwait("foo", GeoLocation("bar", Point(1.0, 2.0)))).isEqualTo(1)
}
verify {
operations.add("foo", RedisGeoCommands.GeoLocation("bar", Point(1.0, 2.0)))
operations.add("foo", GeoLocation("bar", Point(1.0, 2.0)))
}
}
@@ -84,14 +93,30 @@ class ReactiveGeoOperationsExtensionsUnitTests {
fun addGeoLocationList() {
val operations = mockk<ReactiveGeoOperations<String, String>>()
every { operations.add(any(), any<List<RedisGeoCommands.GeoLocation<String>>>()) } returns Mono.just(1)
every { operations.add(any(), any<List<GeoLocation<String>>>()) } returns Mono.just(1)
runBlocking {
assertThat(operations.addAndAwait("foo", listOf(RedisGeoCommands.GeoLocation("bar", Point(1.0, 2.0))))).isEqualTo(1)
assertThat(operations.addAndAwait("foo", listOf(GeoLocation("bar", Point(1.0, 2.0))))).isEqualTo(1)
}
verify {
operations.add("foo", listOf(RedisGeoCommands.GeoLocation("bar", Point(1.0, 2.0))))
operations.add("foo", listOf(GeoLocation("bar", Point(1.0, 2.0))))
}
}
@Test
@ExperimentalCoroutinesApi
fun addGeoLocationFlow() {
val operations = mockk<ReactiveGeoOperations<String, String>>()
every { operations.add(any(), any<Publisher<List<GeoLocation<String>>>>()) } returns Flux.just(1)
val flow = flow { emit(listOf(GeoLocation("bar", Point(1.0, 2.0)))) }
runBlocking {
assertThat(operations.add("foo", flow).toList()).contains(1)
}
verify {
operations.add("foo", any<Publisher<List<GeoLocation<String>>>>())
}
}
@@ -246,6 +271,95 @@ class ReactiveGeoOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun radiusAsFlowCircle() {
val operations = mockk<ReactiveGeoOperations<String, String>>()
val result = GeoResult(GeoLocation("bar", Point(1.0, 2.0)), Distance(1.0))
val circle = Circle(1.0, 2.0, 3.0)
every { operations.radius(any(), any()) } returns Flux.just(result)
runBlocking {
assertThat(operations.radiusAsFlow("foo", circle).toList()).contains(result)
}
verify {
operations.radius("foo", circle)
}
}
@Test
@ExperimentalCoroutinesApi
fun radiusAsFlowCircleAndArgs() {
val operations = mockk<ReactiveGeoOperations<String, String>>()
val result = GeoResult(GeoLocation("bar", Point(1.0, 2.0)), Distance(1.0))
val circle = Circle(1.0, 2.0, 3.0)
val args = GeoRadiusCommandArgs.newGeoRadiusArgs()
every { operations.radius(any(), any(), args) } returns Flux.just(result)
runBlocking {
assertThat(operations.radiusAsFlow("foo", circle, args).toList()).contains(result)
}
verify {
operations.radius("foo", circle, args)
}
}
@Test
@ExperimentalCoroutinesApi
fun radiusAsFlowMemberAndRadius() {
val operations = mockk<ReactiveGeoOperations<String, String>>()
val result = GeoResult(GeoLocation("bar", Point(1.0, 2.0)), Distance(1.0))
every { operations.radius(any(), any(), any<Double>()) } returns Flux.just(result)
runBlocking {
assertThat(operations.radiusAsFlow("foo", "bar", 1.0).toList()).contains(result)
}
verify {
operations.radius("foo", "bar", 1.0)
}
}
@Test
@ExperimentalCoroutinesApi
fun radiusAsFlowDistance() {
val operations = mockk<ReactiveGeoOperations<String, String>>()
val result = GeoResult(GeoLocation("bar", Point(1.0, 2.0)), Distance(1.0))
val distance = Distance(2.0)
every { operations.radius(any(), any(), any<Distance>()) } returns Flux.just(result)
runBlocking {
assertThat(operations.radiusAsFlow("foo", "bar", distance).toList()).contains(result)
}
verify {
operations.radius("foo", "bar", distance)
}
}
@Test
@ExperimentalCoroutinesApi
fun radiusAsFlowDistanceAndArgs() {
val operations = mockk<ReactiveGeoOperations<String, String>>()
val result = GeoResult(GeoLocation("bar", Point(1.0, 2.0)), Distance(1.0))
val distance = Distance(2.0)
val args = GeoRadiusCommandArgs.newGeoRadiusArgs()
every { operations.radius(any(), any(), any(), any()) } returns Flux.just(result)
runBlocking {
assertThat(operations.radiusAsFlow("foo", "bar", distance, args).toList()).contains(result)
}
verify {
operations.radius("foo", "bar", distance, args)
}
}
@Test // DATAREDIS-937
fun remove() {

View File

@@ -18,13 +18,16 @@ package org.springframework.data.redis.core
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
/**
* Unit tests for [ReactiveHashOperationsExtensions].
* Unit tests for `ReactiveHashOperationsExtensions`.
*
* @author Mark Paluch
* @author Christoph Strobl
@@ -106,6 +109,21 @@ class ReactiveHashOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun keys() {
val operations = mockk<ReactiveHashOperations<String, String, String>>()
every { operations.keys(any()) } returns Flux.just("bar", "baz")
runBlocking {
assertThat(operations.keysAsFlow("foo").toList()).contains("bar", "baz")
}
verify {
operations.keys("foo")
}
}
@Test // DATAREDIS-937
fun incrementDouble() {
@@ -181,6 +199,56 @@ class ReactiveHashOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun values() {
val operations = mockk<ReactiveHashOperations<String, String, String>>()
every { operations.values(any()) } returns Flux.just("bar", "baz")
runBlocking {
assertThat(operations.valuesAsFlow("foo").toList()).contains("bar","baz")
}
verify {
operations.values("foo")
}
}
@Test
@ExperimentalCoroutinesApi
fun entries() {
val entry = java.util.AbstractMap.SimpleEntry("bar", "baz")
val operations = mockk<ReactiveHashOperations<String, String, String>>()
every { operations.entries(any()) } returns Flux.just(entry)
runBlocking {
assertThat(operations.entriesAsFlow("foo").toList()).contains(entry)
}
verify {
operations.entries("foo")
}
}
@Test
@ExperimentalCoroutinesApi
fun scan() {
val entry = java.util.AbstractMap.SimpleEntry("bar", "baz")
val operations = mockk<ReactiveHashOperations<String, String, String>>()
every { operations.scan(any(), any()) } returns Flux.just(entry)
runBlocking {
assertThat(operations.scanAsFlow("foo").toList()).contains(entry)
}
verify {
operations.scan("foo", ScanOptions.NONE)
}
}
@Test // DATAREDIS-937
fun remove() {

View File

@@ -24,7 +24,7 @@ import org.junit.Test
import reactor.core.publisher.Mono
/**
* Unit tests for [ReactiveHyperLogLogOperationsExtensions]
* Unit tests for `ReactiveHyperLogLogOperationsExtensions`
*
* @author Mark Paluch
*/

View File

@@ -18,19 +18,38 @@ package org.springframework.data.redis.core
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.time.Duration
/**
* Unit tests for [ReactiveListOperationsExtensions]
* Unit tests for `ReactiveListOperationsExtensions`
*
* @author Mark Paluch
*/
class ReactiveListOperationsExtensionsUnitTests {
@Test
@ExperimentalCoroutinesApi
fun range() {
val operations = mockk<ReactiveListOperations<String, String>>()
every { operations.range(any(), any(), any()) } returns Flux.just("foo", "bar")
runBlocking {
assertThat(operations.rangeAsFlow("foo", 2, 3).toList()).contains("foo", "bar")
}
verify {
operations.range("foo", 2, 3)
}
}
@Test // DATAREDIS-937
fun trim() {

View File

@@ -18,22 +18,83 @@ package org.springframework.data.redis.core
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.springframework.data.redis.connection.DataType
import org.springframework.data.redis.connection.ReactiveSubscription
import org.springframework.data.redis.core.script.RedisScript
import org.springframework.data.redis.listener.ChannelTopic
import org.springframework.data.redis.serializer.RedisElementReader
import org.springframework.data.redis.serializer.RedisElementWriter
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.time.Duration
import java.time.Instant
/**
* Unit tests for [ReactiveRedisOperationsExtensions].
* Unit tests for `ReactiveRedisOperationsExtensions`.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
class ReactiveRedisOperationsExtensionsUnitTests {
@Test
@ExperimentalCoroutinesApi
fun `execute with calllback`() {
val operations = mockk<ReactiveRedisOperations<String, String>>()
every { operations.execute(any<ReactiveRedisCallback<*>>()) } returns Flux.just("foo")
runBlocking {
assertThat(operations.executeAsFlow { flow { emit("foo")} }.toList()).contains("foo")
}
verify {
operations.execute(any<ReactiveRedisCallback<*>>())
}
}
@Test
@ExperimentalCoroutinesApi
fun `execute with script`() {
val script = RedisScript.of<String>("foo")
val operations = mockk<ReactiveRedisOperations<String, String>>()
every { operations.execute(any<RedisScript<*>>(), any(), any()) } returns Flux.just("foo")
runBlocking {
assertThat(operations.executeAsFlow(script).toList()).contains("foo")
}
verify {
operations.execute(script, any(), any())
}
}
@Test
@ExperimentalCoroutinesApi
fun `execute with script, argsWriter and resultReader`() {
val script = RedisScript.of<String>("foo")
val argsWriter = mockk<RedisElementWriter<Any>>(relaxed = true)
val resultReader = mockk<RedisElementReader<String>>(relaxed = true)
val operations = mockk<ReactiveRedisOperations<String, String>>()
every { operations.execute(any<RedisScript<*>>(), any(), any(), any(), any()) } returns Flux.just("foo")
runBlocking {
assertThat(operations.executeAsFlow(script, argsWriter = argsWriter, resultReader = resultReader).toList()).contains("foo")
}
verify {
operations.execute(script, any(), any(), argsWriter, resultReader)
}
}
@Test // DATAREDIS-937
fun convertAndSend() {
@@ -49,6 +110,59 @@ class ReactiveRedisOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun listenToChannel() {
val message = ReactiveSubscription.ChannelMessage("a", "b")
val operations = mockk<ReactiveRedisOperations<String, String>>()
every { operations.listenToChannel(any(), any()) } returns Flux.just(message)
runBlocking {
assertThat(operations.listenToChannelAsFlow("foo", "bar").toList()).contains(message)
}
verify {
operations.listenToChannel("foo", "bar")
}
}
@Test
@ExperimentalCoroutinesApi
fun listenToPattern() {
val message = ReactiveSubscription.ChannelMessage("a", "b")
val operations = mockk<ReactiveRedisOperations<String, String>>()
every { operations.listenToPattern(any(), any()) } returns Flux.just(message)
runBlocking {
assertThat(operations.listenToPatternAsFlow("foo", "bar").toList()).contains(message)
}
verify {
operations.listenToPattern("foo", "bar")
}
}
@Test
@ExperimentalCoroutinesApi
fun listenTo() {
val topic1 = ChannelTopic.of("foo")
val topic2 = ChannelTopic.of("bar")
val message = ReactiveSubscription.ChannelMessage("a", "b")
val operations = mockk<ReactiveRedisOperations<String, String>>()
every { operations.listenTo(any(), any()) } returns Flux.just(message)
runBlocking {
assertThat(operations.listenToAsFlow(topic1, topic2).toList()).contains(message)
}
verify {
operations.listenTo(topic1, topic2)
}
}
@Test // DATAREDIS-937
fun hasKey() {
@@ -79,6 +193,39 @@ class ReactiveRedisOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun keys() {
val operations = mockk<ReactiveRedisOperations<String, String>>()
every { operations.keys(any()) } returns Flux.just("bar")
runBlocking {
assertThat(operations.keysAsFlow("foo").toList()).contains("bar")
}
verify {
operations.keys("foo")
}
}
@Test
@ExperimentalCoroutinesApi
fun scan() {
val operations = mockk<ReactiveRedisOperations<String, String>>()
every { operations.scan(ScanOptions.NONE) } returns Flux.just("foo")
runBlocking {
assertThat(operations.scanAsFlow().toList()).contains("foo")
}
verify {
operations.scan(ScanOptions.NONE)
}
}
@Test // DATAREDIS-937
fun randomKey() {

View File

@@ -18,13 +18,16 @@ package org.springframework.data.redis.core
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
/**
* Unit tests for [ReactiveSetOperationsExtensions].
* Unit tests for `ReactiveSetOperationsExtensions`.
*
* @author Mark Paluch
* @author Christoph Strobl
@@ -76,6 +79,22 @@ class ReactiveSetOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun `pop as Flow`() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.pop(any(), any()) } returns Flux.just("bar")
runBlocking {
assertThat(operations.popAsFlow("foo", 1).toList()).contains("bar")
}
verify {
operations.pop("foo", 1)
}
}
@Test // DATAREDIS-937
fun `pop returning an empty Mono`() {
@@ -136,6 +155,51 @@ class ReactiveSetOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun intersect() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.intersect("foo", "bar") } returns Flux.just("baz")
runBlocking {
assertThat(operations.intersectAsFlow("foo", "bar").toList()).contains("baz")
}
verify {
operations.intersect("foo", "bar")
}
}
@Test
@ExperimentalCoroutinesApi
fun `intersect with key and collection`() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.intersect("foo", listOf("bar")) } returns Flux.just("baz")
runBlocking {
assertThat(operations.intersectAsFlow("foo", listOf("bar")).toList()).contains("baz")
}
verify {
operations.intersect("foo", listOf("bar"))
}
}
@Test
@ExperimentalCoroutinesApi
fun `intersect with collection`() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.intersect(listOf("bar")) } returns Flux.just("baz")
runBlocking {
assertThat(operations.intersectAsFlow(listOf("bar")).toList()).contains("baz")
}
verify {
operations.intersect(listOf("bar"))
}
}
@Test // DATAREDIS-937
fun intersectAndStore() {
@@ -166,6 +230,51 @@ class ReactiveSetOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun union() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.union("foo", "bar") } returns Flux.just("baz")
runBlocking {
assertThat(operations.unionAsFlow("foo", "bar").toList()).contains("baz")
}
verify {
operations.union("foo", "bar")
}
}
@Test
@ExperimentalCoroutinesApi
fun `union with key and collection`() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.union("foo", listOf("bar")) } returns Flux.just("baz")
runBlocking {
assertThat(operations.unionAsFlow("foo", listOf("bar")).toList()).contains("baz")
}
verify {
operations.union("foo", listOf("bar"))
}
}
@Test
@ExperimentalCoroutinesApi
fun `union with collection`() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.union(listOf("bar")) } returns Flux.just("baz")
runBlocking {
assertThat(operations.unionAsFlow(listOf("bar")).toList()).contains("baz")
}
verify {
operations.union(listOf("bar"))
}
}
@Test // DATAREDIS-937
fun unionAndStore() {
@@ -196,6 +305,51 @@ class ReactiveSetOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun difference() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.difference("foo", "bar") } returns Flux.just("baz")
runBlocking {
assertThat(operations.differenceAsFlow("foo", "bar").toList()).contains("baz")
}
verify {
operations.difference("foo", "bar")
}
}
@Test
@ExperimentalCoroutinesApi
fun `difference with key and collection`() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.difference("foo", listOf("bar")) } returns Flux.just("baz")
runBlocking {
assertThat(operations.differenceAsFlow("foo", listOf("bar")).toList()).contains("baz")
}
verify {
operations.difference("foo", listOf("bar"))
}
}
@Test
@ExperimentalCoroutinesApi
fun `difference with collection`() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.difference(listOf("bar")) } returns Flux.just("baz")
runBlocking {
assertThat(operations.differenceAsFlow(listOf("bar")).toList()).contains("baz")
}
verify {
operations.difference(listOf("bar"))
}
}
@Test // DATAREDIS-937
fun differenceAndStore() {
@@ -226,6 +380,37 @@ class ReactiveSetOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun members() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.members("foo") } returns Flux.just("baz")
runBlocking {
assertThat(operations.membersAsFlow("foo").toList()).contains("baz")
}
verify {
operations.members("foo")
}
}
@Test
@ExperimentalCoroutinesApi
fun scan() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.scan(any(), any()) } returns Flux.just("bar")
runBlocking {
assertThat(operations.scanAsFlow("foo").toList()).contains("bar")
}
verify {
operations.scan("foo", ScanOptions.NONE)
}
}
@Test // DATAREDIS-937
fun randomMember() {
@@ -256,6 +441,38 @@ class ReactiveSetOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun distinctRandomMembers() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.distinctRandomMembers(any(), any()) } returns Flux.just("bar")
runBlocking {
assertThat(operations.distinctRandomMembersAsFlow("foo", 1).toList()).contains("bar")
}
verify {
operations.distinctRandomMembers("foo", 1)
}
}
@Test
@ExperimentalCoroutinesApi
fun randomMembers() {
val operations = mockk<ReactiveSetOperations<String, String>>()
every { operations.randomMembers(any(), any()) } returns Flux.just("bar")
runBlocking {
assertThat(operations.randomMembersAsFlow("foo", 1).toList()).contains("bar")
}
verify {
operations.randomMembers("foo", 1)
}
}
@Test // DATAREDIS-937
fun delete() {

View File

@@ -18,14 +18,21 @@ package org.springframework.data.redis.core
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.reactivestreams.Publisher
import org.springframework.data.domain.Range
import org.springframework.data.redis.connection.RedisZSetCommands.Limit
import org.springframework.data.redis.connection.stream.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
/**
* Unit tests for [ReactiveStreamOperationsExtensions].
* Unit tests for `ReactiveStreamOperationsExtensions`.
*
* @author Mark Paluch
*/
@@ -82,11 +89,11 @@ class ReactiveStreamOperationsExtensionsUnitTests {
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
val record = MapRecord.create("foo", mapOf("a" to "b"))
val redordId = RecordId.of("0-0")
every { operations.add(record) } returns Mono.just(redordId)
val recordId = RecordId.of("0-0")
every { operations.add(record) } returns Mono.just(recordId)
runBlocking {
assertThat(operations.addAndAwait(record)).isEqualTo(redordId)
assertThat(operations.addAndAwait(record)).isEqualTo(recordId)
}
verify {
@@ -94,6 +101,26 @@ class ReactiveStreamOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun `add as Flow`() {
val map = mapOf("a" to "b")
val bodyPublisher = Mono.just(map)
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
val recordId = RecordId.of("0-0")
every { operations.add(any(), any<Publisher<Map<String, String>>>()) } returns Flux.just(recordId)
runBlocking {
val bodyFlow = flow { emit(map) }
assertThat(operations.add("foo", bodyFlow).toList()).contains(recordId)
}
verify {
operations.add("foo", any<Publisher<Map<String, String>>>())
}
}
@Test // DATAREDIS-937
fun addRecord() {
@@ -233,6 +260,230 @@ class ReactiveStreamOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun range() {
val record = MapRecord.create("foo", mapOf("a" to "b"))
val range = Range.just("bar")
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.range(any(), any(), any()) } returns Flux.just(record)
runBlocking {
assertThat(operations.rangeAsFlow("foo", range).toList()).contains(record)
}
verify {
operations.range("foo", range, Limit.unlimited())
}
}
@Test
@ExperimentalCoroutinesApi
fun rangeWithType() {
val record = ObjectRecord.create("a", "b")
val range = Range.just("bar")
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.range(any<Class<*>>(), any(), any(), any()) } returns Flux.just(record)
runBlocking {
assertThat(operations.rangeWithTypeAsFlow<String, String>("foo", range).toList()).contains(record)
}
verify {
operations.range(String::class.java, "foo", range, Limit.unlimited())
}
}
@Test
@ExperimentalCoroutinesApi
fun `read with StreamOffset vararg`() {
val offset1 = StreamOffset.create("foo", ReadOffset.lastConsumed())
val offset2 = StreamOffset.create("bar", ReadOffset.lastConsumed())
val record = MapRecord.create("foo", mapOf("a" to "b"))
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.read(offset1, offset2) } returns Flux.just(record)
runBlocking {
assertThat(operations.readAsFlow(offset1, offset2).toList()).contains(record)
}
verify {
operations.read(offset1, offset2)
}
}
@Test
@ExperimentalCoroutinesApi
fun `read with options and StreamOffset vararg` () {
val offset1 = StreamOffset.create("foo", ReadOffset.lastConsumed())
val offset2 = StreamOffset.create("bar", ReadOffset.lastConsumed())
val options = StreamReadOptions.empty()
val record = MapRecord.create("foo", mapOf("a" to "b"))
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.read(options, offset1, offset2) } returns Flux.just(record)
runBlocking {
assertThat(operations.readAsFlow(options, offset1, offset2).toList()).contains(record)
}
verify {
operations.read(options, offset1, offset2)
}
}
@Test
@ExperimentalCoroutinesApi
fun `read with type and StreamOffset vararg`() {
val offset1 = StreamOffset.create("foo", ReadOffset.lastConsumed())
val offset2 = StreamOffset.create("bar", ReadOffset.lastConsumed())
val record = ObjectRecord.create("a", "b")
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.read(String::class.java, offset1, offset2) } returns Flux.just(record)
runBlocking {
assertThat(operations.readWithTypeAsFlow<String, String>(offset1, offset2).toList()).contains(record)
}
verify {
operations.read(String::class.java, offset1, offset2)
}
}
@Test
@ExperimentalCoroutinesApi
fun `read with type, options and StreamOffset vararg` () {
val offset1 = StreamOffset.create("foo", ReadOffset.lastConsumed())
val offset2 = StreamOffset.create("bar", ReadOffset.lastConsumed())
val options = StreamReadOptions.empty()
val record = ObjectRecord.create("a", "b")
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.read(String::class.java, options, offset1, offset2) } returns Flux.just(record)
runBlocking {
assertThat(operations.readWithTypeAsFlow<String, String>(options, offset1, offset2).toList()).contains(record)
}
verify {
operations.read(String::class.java, options, offset1, offset2)
}
}
@Test
@ExperimentalCoroutinesApi
fun `read with consumer and StreamOffset vararg`() {
val consumer = Consumer.from("a", "b")
val offset1 = StreamOffset.create("foo", ReadOffset.lastConsumed())
val offset2 = StreamOffset.create("bar", ReadOffset.lastConsumed())
val record = MapRecord.create("foo", mapOf("a" to "b"))
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.read(consumer, offset1, offset2) } returns Flux.just(record)
runBlocking {
assertThat(operations.readAsFlow(consumer, offset1, offset2).toList()).contains(record)
}
verify {
operations.read(consumer, offset1, offset2)
}
}
@Test
@ExperimentalCoroutinesApi
fun `read with consumer, options and StreamOffset vararg`() {
val consumer = Consumer.from("a", "b")
val offset1 = StreamOffset.create("foo", ReadOffset.lastConsumed())
val offset2 = StreamOffset.create("bar", ReadOffset.lastConsumed())
val options = StreamReadOptions.empty()
val record = MapRecord.create("foo", mapOf("a" to "b"))
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.read(consumer, options, offset1, offset2) } returns Flux.just(record)
runBlocking {
assertThat(operations.readAsFlow(consumer, options, offset1, offset2).toList()).contains(record)
}
verify {
operations.read(consumer, options, offset1, offset2)
}
}
@Test
@ExperimentalCoroutinesApi
fun `read with type, consumer and StreamOffset vararg`() {
val consumer = Consumer.from("a", "b")
val offset1 = StreamOffset.create("foo", ReadOffset.lastConsumed())
val offset2 = StreamOffset.create("bar", ReadOffset.lastConsumed())
val record = ObjectRecord.create("a", "b")
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.read(String::class.java, consumer, offset1, offset2) } returns Flux.just(record)
runBlocking {
assertThat(operations.readWithTypeAsFlow<String, String>(consumer, offset1, offset2).toList()).contains(record)
}
verify {
operations.read(String::class.java, consumer, offset1, offset2)
}
}
@Test
@ExperimentalCoroutinesApi
fun `read with type, consumer, options and StreamOffset vararg`() {
val consumer = Consumer.from("a", "b")
val offset1 = StreamOffset.create("foo", ReadOffset.lastConsumed())
val offset2 = StreamOffset.create("bar", ReadOffset.lastConsumed())
val options = StreamReadOptions.empty()
val record = ObjectRecord.create("a", "b")
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.read(String::class.java, consumer, options, offset1, offset2) } returns Flux.just(record)
runBlocking {
assertThat(operations.readWithTypeAsFlow<String, String>(consumer, options, offset1, offset2).toList()).contains(record)
}
verify {
operations.read(String::class.java, consumer, options, offset1, offset2)
}
}
@Test
@ExperimentalCoroutinesApi
fun reverseRange() {
val record = MapRecord.create("foo", mapOf("a" to "b"))
val range = Range.just("bar")
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.reverseRange(any(), any(), any()) } returns Flux.just(record)
runBlocking {
assertThat(operations.reverseRangeAsFlow("foo", range).toList()).contains(record)
}
verify {
operations.reverseRange("foo", range, Limit.unlimited())
}
}
@Test
@ExperimentalCoroutinesApi
fun reverseRangeWithType() {
val record = ObjectRecord.create("a", "b")
val range = Range.just("bar")
val operations = mockk<ReactiveStreamOperations<String, String, String>>()
every { operations.reverseRange(any<Class<*>>(), any(), any(), any()) } returns Flux.just(record)
runBlocking {
assertThat(operations.reverseRangeWithTypeAsFlow<String, String>("foo", range).toList()).contains(record)
}
verify {
operations.reverseRange(String::class.java, "foo", range, Limit.unlimited())
}
}
@Test // DATAREDIS-937
fun trim() {

View File

@@ -26,7 +26,7 @@ import reactor.core.publisher.Mono
import java.time.Duration
/**
* Unit tests for [ReactiveValueOperationsExtensions].
* Unit tests for `ReactiveValueOperationsExtensions`.
*
* @author Mark Paluch
* @author Christoph Strobl

View File

@@ -18,16 +18,20 @@ package org.springframework.data.redis.core
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.springframework.data.domain.Range
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate
import org.springframework.data.redis.connection.RedisZSetCommands.Weights
import org.springframework.data.redis.core.ZSetOperations.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
/**
* Unit tests for [ReactiveZSetOperationsExtensions].
* Unit tests for `ReactiveZSetOperationsExtensions`.
*
* @author Mark Paluch
* @author Christoph Strobl
@@ -154,6 +158,146 @@ class ReactiveZSetOperationsExtensionsUnitTests {
}
}
@Test
@ExperimentalCoroutinesApi
fun range() {
val range = Range.unbounded<Long>()
val operations = mockk<ReactiveZSetOperations<String, String>>()
every { operations.range(any(), any()) } returns Flux.just("bar")
runBlocking {
assertThat(operations.rangeAsFlow("foo", range).toList()).contains("bar")
}
verify {
operations.range("foo", range)
}
}
@Test
@ExperimentalCoroutinesApi
fun rangeWithScores() {
val tuple = mockk<TypedTuple<String>>(relaxed = true)
val range = Range.unbounded<Long>()
val operations = mockk<ReactiveZSetOperations<String, String>>()
every { operations.rangeWithScores(any(), any()) } returns Flux.just(tuple)
runBlocking {
assertThat(operations.rangeWithScoresAsFlow("foo", range).toList()).contains(tuple)
}
verify {
operations.rangeWithScores("foo", range)
}
}
@Test
@ExperimentalCoroutinesApi
fun rangeByScore() {
val range = Range.unbounded<Double>()
val operations = mockk<ReactiveZSetOperations<String, String>>()
every { operations.rangeByScore(any(), any()) } returns Flux.just("bar")
runBlocking {
assertThat(operations.rangeByScoreAsFlow("foo", range).toList()).contains("bar")
}
verify {
operations.rangeByScore("foo", range)
}
}
@Test
@ExperimentalCoroutinesApi
fun rangeByScoreWithScores() {
val tuple = mockk<TypedTuple<String>>(relaxed = true)
val range = Range.unbounded<Double>()
val operations = mockk<ReactiveZSetOperations<String, String>>()
every { operations.rangeByScoreWithScores(any(), any()) } returns Flux.just(tuple)
runBlocking {
assertThat(operations.rangeByScoreWithScoresAsFlow("foo", range).toList()).contains(tuple)
}
verify {
operations.rangeByScoreWithScores("foo", range)
}
}
@Test
@ExperimentalCoroutinesApi
fun reverseRange() {
val range = Range.unbounded<Long>()
val operations = mockk<ReactiveZSetOperations<String, String>>()
every { operations.reverseRange(any(), any()) } returns Flux.just("bar")
runBlocking {
assertThat(operations.reverseRangeAsFlow("foo", range).toList()).contains("bar")
}
verify {
operations.reverseRange("foo", range)
}
}
@Test
@ExperimentalCoroutinesApi
fun reverseRangeWithScores() {
val tuple = mockk<TypedTuple<String>>(relaxed = true)
val range = Range.unbounded<Long>()
val operations = mockk<ReactiveZSetOperations<String, String>>()
every { operations.reverseRangeWithScores(any(), any()) } returns Flux.just(tuple)
runBlocking {
assertThat(operations.reverseRangeWithScoresAsFlow("foo", range).toList()).contains(tuple)
}
verify {
operations.reverseRangeWithScores("foo", range)
}
}
@Test
@ExperimentalCoroutinesApi
fun reverseRangeByScore() {
val range = Range.unbounded<Double>()
val operations = mockk<ReactiveZSetOperations<String, String>>()
every { operations.reverseRangeByScore(any(), any()) } returns Flux.just("bar")
runBlocking {
assertThat(operations.reverseRangeByScoreAsFlow("foo", range).toList()).contains("bar")
}
verify {
operations.reverseRangeByScore("foo", range)
}
}
@Test
@ExperimentalCoroutinesApi
fun reverseRangeByScoreWithScores() {
val tuple = mockk<TypedTuple<String>>(relaxed = true)
val range = Range.unbounded<Double>()
val operations = mockk<ReactiveZSetOperations<String, String>>()
every { operations.reverseRangeByScoreWithScores(any(), any()) } returns Flux.just(tuple)
runBlocking {
assertThat(operations.reverseRangeByScoreWithScoresAsFlow("foo", range).toList()).contains(tuple)
}
verify {
operations.reverseRangeByScoreWithScores("foo", range)
}
}
@Test // DATAREDIS-937
fun count() {