diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index ca9cf4962..d2c2cfc4f 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -7,7 +7,7 @@ This section briefly covers items that are new and noteworthy in the latest rele == New in Spring Data Redis 2.6 * Support for `SubscriptionListener` when using `MessageListener` for subscription confirmation callbacks. `ReactiveRedisMessageListenerContainer` and `ReactiveRedisOperations` provide `receiveLater(…)` and `listenToLater(…)` methods to await until Redis acknowledges the subscription. -* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`, `COPY`, `GETEX`, `GETDEL`, `ZPOPMIN`, `BZPOPMIN`, `ZPOPMAX`, `BZPOPMAX`, `ZMSCORE`, `ZDIFF`, `ZDIFFSTORE`, `ZINTER`, `ZUNION`, `HRANDFIELD`). +* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`, `COPY`, `GETEX`, `GETDEL`, `ZPOPMIN`, `BZPOPMIN`, `ZPOPMAX`, `BZPOPMAX`, `ZMSCORE`, `ZDIFF`, `ZDIFFSTORE`, `ZINTER`, `ZUNION`, `HRANDFIELD`, `ZRANDMEMBER`). [[new-in-2.5.0]] == New in Spring Data Redis 2.5 diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index 095e7e505..89962ad55 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -85,6 +85,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco private final TupleConverter tupleConverter = new TupleConverter(); private SetConverter tupleToStringTuple = new SetConverter<>(tupleConverter); private SetConverter stringTupleToTuple = new SetConverter<>(new StringTupleConverter()); + private ListConverter tupleListToStringTuple = new ListConverter<>(new TupleConverter()); private ListConverter byteListToStringList = new ListConverter<>(bytesToString); private MapConverter byteMapToStringMap = new MapConverter<>(bytesToString); private MapConverter stringMapToByteMap = new MapConverter<>(stringToBytes); @@ -3296,6 +3297,78 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return zInterStore(serialize(destKey), serializeMulti(sets)); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMember(byte[]) + */ + @Override + public byte[] zRandMember(byte[] key) { + return delegate.zRandMember(key); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMember(byte[], long) + */ + @Override + public List zRandMember(byte[] key, long count) { + return delegate.zRandMember(key, count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMemberWithScore(byte[]) + */ + @Override + public Tuple zRandMemberWithScore(byte[] key) { + return delegate.zRandMemberWithScore(key); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMemberWithScore(byte[], long) + */ + @Override + public List zRandMemberWithScore(byte[] key, long count) { + return delegate.zRandMemberWithScore(key, count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zRandMember(java.lang.String) + */ + @Override + public String zRandMember(String key) { + return convertAndReturn(delegate.zRandMember(serialize(key)), bytesToString); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zRandMember(java.lang.String, long) + */ + @Override + public List zRandMember(String key, long count) { + return convertAndReturn(delegate.zRandMember(serialize(key), count), byteListToStringList); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zRandMemberWithScore(java.lang.String) + */ + @Override + public StringTuple zRandMemberWithScore(String key) { + return convertAndReturn(delegate.zRandMemberWithScore(serialize(key)), new TupleConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zRandMemberWithScores(java.lang.String, long) + */ + @Override + public List zRandMemberWithScores(String key, long count) { + return convertAndReturn(delegate.zRandMemberWithScore(serialize(key), count), tupleListToStringTuple); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#zRange(java.lang.String, long, long) diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java index 7d39e7479..c8923e4b6 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -1068,6 +1068,34 @@ public interface DefaultedRedisConnection extends RedisConnection { return zSetCommands().zInterStore(destKey, sets); } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default byte[] zRandMember(byte[] key) { + return zSetCommands().zRandMember(key); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default List zRandMember(byte[] key, long count) { + return zSetCommands().zRandMember(key, count); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Tuple zRandMemberWithScore(byte[] key) { + return zSetCommands().zRandMemberWithScore(key); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default List zRandMemberWithScore(byte[] key, long count) { + return zSetCommands().zRandMemberWithScore(key, count); + } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java index e73dcaa8d..367120cf9 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java @@ -477,6 +477,142 @@ public interface ReactiveZSetCommands { */ Flux> zIncrBy(Publisher commands); + /** + * {@code ZRANDMEMBER} command parameters. + * + * @author Mark Paluch + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + class ZRandMemberCommand extends KeyCommand { + + private final long count; + + private ZRandMemberCommand(@Nullable ByteBuffer key, long count) { + + super(key); + this.count = count; + } + + /** + * Creates a new {@link ZRandMemberCommand} given the number of values to retrieve. + * + * @param nrValuesToRetrieve + * @return a new {@link ZRandMemberCommand} for a number of values to retrieve. + */ + public static ZRandMemberCommand valueCount(long nrValuesToRetrieve) { + return new ZRandMemberCommand(null, nrValuesToRetrieve); + } + + /** + * Creates a new {@link ZRandMemberCommand} to retrieve one random member. + * + * @return a new {@link ZRandMemberCommand} to retrieve one random member. + */ + public static ZRandMemberCommand singleValue() { + return new ZRandMemberCommand(null, 1); + } + + /** + * Applies the {@literal key}. Constructs a new command instance with all previously configured properties. + * + * @param key must not be {@literal null}. + * @return a new {@link ZRandMemberCommand} with {@literal key} applied. + */ + public ZRandMemberCommand from(ByteBuffer key) { + + Assert.notNull(key, "Key must not be null!"); + + return new ZRandMemberCommand(key, count); + } + + /** + * @return + */ + public long getCount() { + return count; + } + } + + /** + * Get random element from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + default Mono zRandMember(ByteBuffer key) { + return zRandMember(Mono.just(ZRandMemberCommand.singleValue().from(key))).flatMap(CommandResponse::getOutput) + .next(); + } + + /** + * Get {@code count} random elements from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count if the provided {@code count} argument is positive, return a list of distinct fields, capped either at + * {@code count} or the set size. If {@code count} is negative, the behavior changes and the command is + * allowed to return the same value multiple times. In this case, the number of returned values is the + * absolute value of the specified count. + * @return + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + default Flux zRandMember(ByteBuffer key, long count) { + return zRandMember(Mono.just(ZRandMemberCommand.valueCount(count).from(key))).flatMap(CommandResponse::getOutput); + } + + /** + * Get random elements from sorted set at {@code key}. + * + * @param commands must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + Flux>> zRandMember(Publisher commands); + + /** + * Get random element from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + default Mono zRandMemberWithScore(ByteBuffer key) { + return zRandMemberWithScore(Mono.just(ZRandMemberCommand.singleValue().from(key))) + .flatMap(CommandResponse::getOutput).next(); + } + + /** + * Get {@code count} random elements from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count if the provided {@code count} argument is positive, return a list of distinct fields, capped either at + * {@code count} or the set size. If {@code count} is negative, the behavior changes and the command is + * allowed to return the same value multiple times. In this case, the number of returned values is the + * absolute value of the specified count. + * @return + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + default Flux zRandMemberWithScore(ByteBuffer key, long count) { + return zRandMemberWithScore(Mono.just(ZRandMemberCommand.valueCount(count).from(key))) + .flatMap(CommandResponse::getOutput); + } + + /** + * Get random elements from sorted set at {@code key}. + * + * @param commands must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + Flux>> zRandMemberWithScore(Publisher commands); + /** * {@code ZRANK}/{@literal ZREVRANK} command parameters. * diff --git a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java index 07b37c720..9c299a797 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java @@ -634,6 +634,58 @@ public interface RedisZSetCommands { @Nullable Double zIncrBy(byte[] key, double increment, byte[] value); + /** + * Get random element from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return can be {@literal null}. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + byte[] zRandMember(byte[] key); + + /** + * Get {@code count} random elements from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count if the provided {@code count} argument is positive, return a list of distinct fields, capped either at + * {@code count} or the set size. If {@code count} is negative, the behavior changes and the command is + * allowed to return the same value multiple times. In this case, the number of returned values is the + * absolute value of the specified count. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + List zRandMember(byte[] key, long count); + + /** + * Get random element from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return can be {@literal null}. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + Tuple zRandMemberWithScore(byte[] key); + + /** + * Get {@code count} random elements from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count if the provided {@code count} argument is positive, return a list of distinct fields, capped either at + * {@code count} or the set size. If {@code count} is negative, the behavior changes and the command is + * allowed to return the same value multiple times. In this case, the number of returned values is the + * absolute value of the specified count. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + List zRandMemberWithScore(byte[] key, long count); + /** * Determine the index of element with {@code value} in a sorted set. * diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java index 156af1909..66cf8a131 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -1229,6 +1229,58 @@ public interface StringRedisConnection extends RedisConnection { */ Double zIncrBy(String key, double increment, String value); + /** + * Get random element from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return can be {@literal null}. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + String zRandMember(String key); + + /** + * Get {@code count} random elements from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count if the provided {@code count} argument is positive, return a list of distinct fields, capped either at + * {@code count} or the set size. If {@code count} is negative, the behavior changes and the command is + * allowed to return the same value multiple times. In this case, the number of returned values is the + * absolute value of the specified count. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + List zRandMember(String key, long count); + + /** + * Get random element from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return can be {@literal null}. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + StringTuple zRandMemberWithScore(String key); + + /** + * Get {@code count} random elements from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count if the provided {@code count} argument is positive, return a list of distinct fields, capped either at + * {@code count} or the set size. If {@code count} is negative, the behavior changes and the command is + * allowed to return the same value multiple times. In this case, the number of returned values is the + * absolute value of the specified count. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + List zRandMemberWithScores(String key, long count); + /** * Determine the index of element with {@code value} in a sorted set. * diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java index eee02bf5a..e044a3219 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java @@ -18,9 +18,11 @@ package org.springframework.data.redis.connection.jedis; import redis.clients.jedis.ScanParams; import redis.clients.jedis.ZParams; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -124,6 +126,74 @@ class JedisClusterZSetCommands implements RedisZSetCommands { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMember(byte[]) + */ + @Override + public byte[] zRandMember(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + return connection.getCluster().zrandmember(key); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMember(byte[], long) + */ + @Override + public List zRandMember(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + try { + return new ArrayList<>(connection.getCluster().zrandmember(key, count)); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMemberWithScore(byte[]) + */ + @Override + public Tuple zRandMemberWithScore(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + Set tuples = connection.getCluster().zrandmemberWithScores(key, 1); + + return tuples.isEmpty() ? null : JedisConverters.toTuple(tuples.iterator().next()); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMemberWithScore(byte[], long) + */ + @Override + public List zRandMemberWithScore(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + try { + Set tuples = connection.getCluster().zrandmemberWithScores(key, count); + + return tuples.stream().map(JedisConverters::toTuple).collect(Collectors.toList()); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRank(byte[], byte[]) diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java index 7fb2a4e53..906070b18 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java @@ -62,7 +62,8 @@ class JedisZSetCommands implements RedisZSetCommands { Assert.notNull(key, "Key must not be null!"); Assert.notNull(value, "Value must not be null!"); - return connection.invoke().from(BinaryJedis::zadd, MultiKeyPipelineBase::zadd, key, score, value, JedisConverters.toZAddParams(args)) + return connection.invoke() + .from(BinaryJedis::zadd, MultiKeyPipelineBase::zadd, key, score, value, JedisConverters.toZAddParams(args)) .get(JedisConverters::toBoolean); } @@ -107,6 +108,65 @@ class JedisZSetCommands implements RedisZSetCommands { return connection.invoke().just(BinaryJedis::zincrby, MultiKeyPipelineBase::zincrby, key, increment, value); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMember(byte[]) + */ + @Override + public byte[] zRandMember(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().just(BinaryJedis::zrandmember, MultiKeyPipelineBase::zrandmember, key); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMember(byte[], long) + */ + @Override + public List zRandMember(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().fromMany(BinaryJedis::zrandmember, MultiKeyPipelineBase::zrandmember, key, count) + .toList(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMemberWithScore(byte[]) + */ + @Override + public Tuple zRandMemberWithScore(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke() + .from(BinaryJedis::zrandmemberWithScores, MultiKeyPipelineBase::zrandmemberWithScores, key, 1L).get(it -> { + + if (it.isEmpty()) { + return null; + } + + return JedisConverters.toTuple(it.iterator().next()); + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMemberWithScore(byte[], long) + */ + @Override + public List zRandMemberWithScore(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke() + .fromMany(BinaryJedis::zrandmemberWithScores, MultiKeyPipelineBase::zrandmemberWithScores, key, count) + .toList(JedisConverters::toTuple); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRank(byte[], byte[]) diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java index 9ce78ad3d..10f79ffdb 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java @@ -160,6 +160,39 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands { })); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRandMember(Publisher) + */ + @Override + public Flux>> zRandMember( + Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notNull(command.getKey(), "Key must not be null!"); + + return new CommandResponse<>(command, cmd.zrandmember(command.getKey(), command.getCount())); + })); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRandMemberWithScore(Publisher) + */ + @Override + public Flux>> zRandMemberWithScore( + Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notNull(command.getKey(), "Key must not be null!"); + + return new CommandResponse<>(command, cmd.zrandmemberWithScores(command.getKey(), command.getCount()) + .map(this::toTuple)); + })); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRank(org.reactivestreams.Publisher) diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java index 2ff55abc5..04b96fab3 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java @@ -107,6 +107,56 @@ class LettuceZSetCommands implements RedisZSetCommands { return connection.invoke().just(RedisSortedSetAsyncCommands::zincrby, key, increment, value); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMember(byte[]) + */ + @Override + public byte[] zRandMember(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().just(RedisSortedSetAsyncCommands::zrandmember, key); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMember(byte[], long) + */ + @Override + public List zRandMember(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().just(RedisSortedSetAsyncCommands::zrandmember, key, count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMemberWithScore(byte[]) + */ + @Override + public Tuple zRandMemberWithScore(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().from(RedisSortedSetAsyncCommands::zrandmemberWithScores, key) + .get(LettuceConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRandMemberWithScore(byte[], long) + */ + @Override + public List zRandMemberWithScore(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zrandmemberWithScores, key, count) + .toList(LettuceConverters::toTuple); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRank(byte[], byte[]) diff --git a/src/main/java/org/springframework/data/redis/core/AbstractOperations.java b/src/main/java/org/springframework/data/redis/core/AbstractOperations.java index 3ec786953..6e9e5c39b 100644 --- a/src/main/java/org/springframework/data/redis/core/AbstractOperations.java +++ b/src/main/java/org/springframework/data/redis/core/AbstractOperations.java @@ -15,6 +15,7 @@ */ package org.springframework.data.redis.core; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; @@ -224,7 +225,7 @@ abstract class AbstractOperations { } @Nullable - Set> deserializeTupleValues(@Nullable Collection rawValues) { + Set> deserializeTupleValues(@Nullable Set rawValues) { if (rawValues == null) { return null; } @@ -235,6 +236,17 @@ abstract class AbstractOperations { return set; } + List> deserializeTupleValues(List rawValues) { + if (rawValues == null) { + return null; + } + List> set = new ArrayList<>(rawValues.size()); + for (Tuple rawValue : rawValues) { + set.add(deserializeTuple(rawValue)); + } + return set; + } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Nullable TypedTuple deserializeTuple(@Nullable Tuple tuple) { diff --git a/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java b/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java index 94ffe72ce..2b706863f 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java @@ -107,6 +107,73 @@ public interface BoundZSetOperations extends BoundKeyOperations { @Nullable Double incrementScore(V value, double delta); + /** + * Get random element from set at the bound key. + * + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + V randomMember(); + + /** + * Get {@code count} distinct random elements from set at the bound key. + * + * @param count nr of members to return + * @return empty {@link Set} if {@code key} does not exist. + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + Set distinctRandomMembers(long count); + + /** + * Get {@code count} random elements from set at the bound key. + * + * @param count nr of members to return. + * @return empty {@link List} if {@code key} does not exist or {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + List randomMembers(long count); + + /** + * Get random element with its score from set at the bound key. + * + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + TypedTuple randomMemberWithScore(); + + /** + * Get {@code count} distinct random elements with their score from set at the bound key. + * + * @param count nr of members to return + * @return empty {@link Set} if {@code key} does not exist. + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + Set> distinctRandomMembersWithScore(long count); + + /** + * Get {@code count} random elements with their score from set at the bound key. + * + * @param key must not be {@literal null}. + * @param count nr of members to return. + * @return empty {@link List} if {@code key} does not exist or {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + List> randomMembersWithScore(long count); + /** * Determine the index of element with {@code value} in a sorted set. * diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java index a12c1287a..103979efc 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java @@ -99,6 +99,64 @@ class DefaultBoundZSetOperations extends DefaultBoundKeyOperations impl return ops.incrementScore(getKey(), value, delta); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#randomMember() + */ + @Override + public V randomMember() { + return ops.randomMember(getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#distinctRandomMembers(long) + */ + @Nullable + @Override + public Set distinctRandomMembers(long count) { + return ops.distinctRandomMembers(getKey(), count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#randomMembers(long) + */ + @Nullable + @Override + public List randomMembers(long count) { + return ops.randomMembers(getKey(), count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#randomMemberWithScore() + */ + @Override + public TypedTuple randomMemberWithScore() { + return ops.randomMemberWithScore(getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#distinctRandomMembersWithScore(long) + */ + @Nullable + @Override + public Set> distinctRandomMembersWithScore(long count) { + return ops.distinctRandomMembersWithScore(getKey(), count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#randomMembersWithScore(long) + */ + @Nullable + @Override + public List> randomMembersWithScore(long count) { + return ops.randomMembersWithScore(getKey(), count); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundZSetOperations#getOperations() diff --git a/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java index 5d018caa4..7b57c80ea 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java @@ -120,6 +120,82 @@ class DefaultReactiveZSetOperations implements ReactiveZSetOperations connection.zIncrBy(rawKey(key), delta, rawValue(value))); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#randomMember(K) + */ + @Override + public Mono randomMember(K key) { + + Assert.notNull(key, "Key must not be null!"); + + return createMono(connection -> connection.zRandMember(rawKey(key))).map(this::readValue); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#distinctRandomMembers(K, long) + */ + @Override + public Flux distinctRandomMembers(K key, long count) { + + Assert.notNull(key, "Key must not be null!"); + Assert.isTrue(count > 0, "Negative count not supported. Use randomMembers to allow duplicate elements."); + + return createFlux(connection -> connection.zRandMember(rawKey(key), count)).map(this::readValue); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#randomMembers(K, long) + */ + @Override + public Flux randomMembers(K key, long count) { + + Assert.notNull(key, "Key must not be null!"); + Assert.isTrue(count > 0, "Use a positive number for count. This method is already allowing duplicate elements."); + + return createFlux(connection -> connection.zRandMember(rawKey(key), -count)).map(this::readValue); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#randomMemberWithScore(K) + */ + @Override + public Mono> randomMemberWithScore(K key) { + + Assert.notNull(key, "Key must not be null!"); + + return createMono(connection -> connection.zRandMemberWithScore(rawKey(key))).map(this::readTypedTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#distinctRandomMembersWithScore(K, long) + */ + @Override + public Flux> distinctRandomMembersWithScore(K key, long count) { + + Assert.notNull(key, "Key must not be null!"); + Assert.isTrue(count > 0, "Negative count not supported. Use randomMembers to allow duplicate elements."); + + return createFlux(connection -> connection.zRandMemberWithScore(rawKey(key), count)).map(this::readTypedTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#randomMembersWithScore(K, long) + */ + @Override + public Flux> randomMembersWithScore(K key, long count) { + + Assert.notNull(key, "Key must not be null!"); + Assert.isTrue(count > 0, "Use a positive number for count. This method is already allowing duplicate elements."); + + return createFlux(connection -> connection.zRandMemberWithScore(rawKey(key), -count)).map(this::readTypedTuple); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveZSetOperations#rank(java.lang.Object, java.lang.Object) diff --git a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java index a1e965d9d..4c4e58848 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.core; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -28,6 +29,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; import org.springframework.data.redis.connection.RedisZSetCommands.Weights; import org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Default implementation of {@link ZSetOperations}. @@ -130,6 +132,90 @@ class DefaultZSetOperations extends AbstractOperations implements ZS return execute(connection -> connection.zIncrBy(rawKey, delta, rawValue), true); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#randomMember(java.lang.Object) + */ + @Override + public V randomMember(K key) { + + byte[] rawKey = rawKey(key); + + return deserializeValue(execute(connection -> connection.zRandMember(rawKey), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#distinctRandomMembers(java.lang.Object, long) + */ + @Override + public Set distinctRandomMembers(K key, long count) { + + Assert.isTrue(count > 0, "Negative count not supported. Use randomMembers to allow duplicate elements."); + + byte[] rawKey = rawKey(key); + + List result = execute(connection -> connection.zRandMember(rawKey, count), true); + return result != null ? deserializeValues(new LinkedHashSet<>(result)) : null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#randomMembers(java.lang.Object, long) + */ + @Override + public List randomMembers(K key, long count) { + + Assert.isTrue(count > 0, "Use a positive number for count. This method is already allowing duplicate elements."); + + byte[] rawKey = rawKey(key); + + List result = execute(connection -> connection.zRandMember(rawKey, count), true); + return deserializeValues(result); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#randomMemberWithScore(java.lang.Object) + */ + @Override + public TypedTuple randomMemberWithScore(K key) { + + byte[] rawKey = rawKey(key); + + return deserializeTuple(execute(connection -> connection.zRandMemberWithScore(rawKey), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#distinctRandomMembersWithScore(java.lang.Object, long) + */ + @Override + public Set> distinctRandomMembersWithScore(K key, long count) { + + Assert.isTrue(count > 0, "Negative count not supported. Use randomMembers to allow duplicate elements."); + + byte[] rawKey = rawKey(key); + + List result = execute(connection -> connection.zRandMemberWithScore(rawKey, count), true); + return result != null ? deserializeTupleValues(new LinkedHashSet<>(result)) : null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#randomMembersWithScore(java.lang.Object, long) + */ + @Override + public List> randomMembersWithScore(K key, long count) { + + Assert.isTrue(count > 0, "Use a positive number for count. This method is already allowing duplicate elements."); + + byte[] rawKey = rawKey(key); + + List result = execute(connection -> connection.zRandMemberWithScore(rawKey, count), true); + return result != null ? deserializeTupleValues(result) : null; + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ZSetOperations#range(java.lang.Object, long, long) @@ -459,7 +545,8 @@ class DefaultZSetOperations extends AbstractOperations implements ZS public Set> popMin(K key, long count) { byte[] rawKey = rawKey(key); - return deserializeTupleValues(execute(connection -> connection.zPopMin(rawKey, count), true)); + Set result = execute(connection -> connection.zPopMin(rawKey, count), true); + return deserializeTupleValues(new LinkedHashSet<> (result)); } /* @@ -495,7 +582,8 @@ class DefaultZSetOperations extends AbstractOperations implements ZS public Set> popMax(K key, long count) { byte[] rawKey = rawKey(key); - return deserializeTupleValues(execute(connection -> connection.zPopMax(rawKey, count), true)); + Set result =execute(connection -> connection.zPopMax(rawKey, count), true); + return deserializeTupleValues(new LinkedHashSet<>(result)); } /* @@ -550,7 +638,8 @@ class DefaultZSetOperations extends AbstractOperations implements ZS public Set> differenceWithScores(K key, Collection otherKeys) { byte[][] rawKeys = rawKeys(key, otherKeys); - return deserializeTupleValues(execute(connection -> connection.zDiffWithScores(rawKeys), true)); + Set result = execute(connection -> connection.zDiffWithScores(rawKeys), true); + return deserializeTupleValues(new LinkedHashSet<>(result)); } /* @@ -586,7 +675,8 @@ class DefaultZSetOperations extends AbstractOperations implements ZS public Set> intersectWithScores(K key, Collection otherKeys) { byte[][] rawKeys = rawKeys(key, otherKeys); - return deserializeTupleValues(execute(connection -> connection.zInterWithScores(rawKeys), true)); + Set result = execute(connection -> connection.zInterWithScores(rawKeys), true); + return deserializeTupleValues(result); } /* @@ -597,8 +687,8 @@ class DefaultZSetOperations extends AbstractOperations implements ZS public Set> intersectWithScores(K key, Collection otherKeys, Aggregate aggregate, Weights weights) { byte[][] rawKeys = rawKeys(key, otherKeys); - return deserializeTupleValues( - execute(connection -> connection.zInterWithScores(aggregate, weights, rawKeys), true)); + Set result = execute(connection -> connection.zInterWithScores(aggregate, weights, rawKeys), true); + return deserializeTupleValues(result); } /* @@ -656,7 +746,8 @@ class DefaultZSetOperations extends AbstractOperations implements ZS public Set> unionWithScores(K key, Collection otherKeys) { byte[][] rawKeys = rawKeys(key, otherKeys); - return deserializeTupleValues(execute(connection -> connection.zUnionWithScores(rawKeys), true)); + Set result = execute(connection -> connection.zUnionWithScores(rawKeys), true); + return deserializeTupleValues(result); } /* @@ -667,8 +758,9 @@ class DefaultZSetOperations extends AbstractOperations implements ZS public Set> unionWithScores(K key, Collection otherKeys, Aggregate aggregate, Weights weights) { byte[][] rawKeys = rawKeys(key, otherKeys); + Set result = execute(connection -> connection.zUnionWithScores(aggregate, weights, rawKeys), true); return deserializeTupleValues( - execute(connection -> connection.zUnionWithScores(aggregate, weights, rawKeys), true)); + result); } /* diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java index 0e4565754..d3eb63431 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java @@ -83,6 +83,74 @@ public interface ReactiveZSetOperations { */ Mono incrementScore(K key, V value, double delta); + /** + * Get random element from set at {@code key}. + * + * @param key must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + Mono randomMember(K key); + + /** + * Get {@code count} distinct random elements from set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count nr of members to return + * @return + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + Flux distinctRandomMembers(K key, long count); + + /** + * Get {@code count} random elements from set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count nr of members to return. + * @return + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + Flux randomMembers(K key, long count); + + /** + * Get random element with its score from set at {@code key}. + * + * @param key must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + Mono> randomMemberWithScore(K key); + + /** + * Get {@code count} distinct random elements with their score from set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count nr of members to return + * @return + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + Flux> distinctRandomMembersWithScore(K key, long count); + + /** + * Get {@code count} random elements with their score from set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count nr of members to return. + * @return + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + Flux> randomMembersWithScore(K key, long count); + /** * Determine the index of element with {@code value} in a sorted set. * diff --git a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java index ff2188602..349a72e34 100644 --- a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java @@ -138,6 +138,78 @@ public interface ZSetOperations { @Nullable Double incrementScore(K key, V value, double delta); + /** + * Get random element from set at {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + V randomMember(K key); + + /** + * Get {@code count} distinct random elements from set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count nr of members to return + * @return empty {@link Set} if {@code key} does not exist. + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + Set distinctRandomMembers(K key, long count); + + /** + * Get {@code count} random elements from set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count nr of members to return. + * @return empty {@link List} if {@code key} does not exist or {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + List randomMembers(K key, long count); + + /** + * Get random element with its score from set at {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + TypedTuple randomMemberWithScore(K key); + + /** + * Get {@code count} distinct random elements with their score from set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count nr of members to return + * @return empty {@link Set} if {@code key} does not exist. + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + Set> distinctRandomMembersWithScore(K key, long count); + + /** + * Get {@code count} random elements with their score from set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count nr of members to return. + * @return empty {@link List} if {@code key} does not exist or {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if count is negative. + * @since 2.6 + * @see Redis Documentation: ZRANDMEMBER + */ + @Nullable + List> randomMembersWithScore(K key, long count); + /** * Determine the index of element with {@code value} in a sorted set. * diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index dbdc8742f..77418679a 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -2026,6 +2026,39 @@ public abstract class AbstractConnectionIntegrationTests { new DefaultStringTuple("James".getBytes(), "James", 12d))) })); } + @Test // GH-2049 + @EnabledOnCommand("ZRANDMEMBER") + void testZRandMember() { + + actual.add(connection.zAdd("myset", 2, "Bob")); + actual.add(connection.zAdd("myset", 1, "James")); + actual.add(connection.zRandMember("myset")); + actual.add(connection.zRandMember("myset", 2)); + + List results = getResults(); + + assertThat(results.get(2)).isNotNull(); + assertThat(new LinkedHashSet<>((Collection) results.get(3))) + .isEqualTo(new LinkedHashSet<>(Arrays.asList("Bob", "James"))); + } + + @Test // GH-2049 + @EnabledOnCommand("ZRANDMEMBER") + void testZRandMemberWithScore() { + + actual.add(connection.zAdd("myset", 2, "Bob")); + actual.add(connection.zAdd("myset", 1, "James")); + actual.add(connection.zRandMemberWithScore("myset")); + actual.add(connection.zRandMemberWithScores("myset", 2)); + + List results = getResults(); + + assertThat(results.get(2)).isNotNull(); + assertThat(new LinkedHashSet<>((Collection) results.get(3))) + .isEqualTo(new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("Bob".getBytes(), "Bob", 2d), + new DefaultStringTuple("James".getBytes(), "James", 1d)))); + } + @Test void testZRangeWithScores() { diff --git a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java index f8c1d7b1f..2302d31ec 100644 --- a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java @@ -637,6 +637,12 @@ public interface ClusterConnectionTests { // GH-2007 void bzPopMaxShouldWorkCorrectly(); + // GH-2049 + void zRandMemberShouldReturnResultCorrectly(); + + // GH-2049 + void zRandMemberWithScoreShouldReturnResultCorrectly(); + // DATAREDIS-315 void zRangeByLexShouldReturnResultCorrectly(); diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java index 158e1c699..a6aa682b4 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java @@ -2139,6 +2139,29 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests { .isEqualTo(new DefaultTuple(VALUE_3_BYTES, 30D)); } + @Test // GH-2049 + @EnabledOnCommand("ZRANDMEMBER") + public void zRandMemberShouldReturnResultCorrectly() { + + nativeConnection.zadd(KEY_1_BYTES, 10D, VALUE_1_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 20D, VALUE_2_BYTES); + + assertThat(clusterConnection.zRandMember(KEY_1_BYTES)).isIn(VALUE_1_BYTES, VALUE_2_BYTES); + assertThat(clusterConnection.zRandMember(KEY_1_BYTES, 2)).hasSize(2).contains(VALUE_1_BYTES, VALUE_2_BYTES); + + } + + @Test // GH-2049 + @EnabledOnCommand("ZRANDMEMBER") + public void zRandMemberWithScoreShouldReturnResultCorrectly() { + + nativeConnection.zadd(KEY_1_BYTES, 10D, VALUE_1_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 20D, VALUE_2_BYTES); + + assertThat(clusterConnection.zRandMemberWithScore(KEY_1_BYTES)).isNotNull(); + assertThat(clusterConnection.zRandMemberWithScore(KEY_1_BYTES, 2)).hasSize(2); + } + @Test // DATAREDIS-315 public void zRangeByLexShouldReturnResultCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java index acb2aa4cf..e89a54a9f 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java @@ -2179,6 +2179,29 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests { .isEqualTo(new DefaultTuple(VALUE_3_BYTES, 30D)); } + @Test // GH-2049 + @EnabledOnCommand("ZRANDMEMBER") + public void zRandMemberShouldReturnResultCorrectly() { + + nativeConnection.zadd(KEY_1, 10D, VALUE_1); + nativeConnection.zadd(KEY_1, 20D, VALUE_2); + + assertThat(clusterConnection.zRandMember(KEY_1_BYTES)).isIn(VALUE_1_BYTES, VALUE_2_BYTES); + assertThat(clusterConnection.zRandMember(KEY_1_BYTES, 2)).hasSize(2).contains(VALUE_1_BYTES, VALUE_2_BYTES); + + } + + @Test // GH-2049 + @EnabledOnCommand("ZRANDMEMBER") + public void zRandMemberWithScoreShouldReturnResultCorrectly() { + + nativeConnection.zadd(KEY_1, 10D, VALUE_1); + nativeConnection.zadd(KEY_1, 20D, VALUE_2); + + assertThat(clusterConnection.zRandMemberWithScore(KEY_1_BYTES)).isNotNull(); + assertThat(clusterConnection.zRandMemberWithScore(KEY_1_BYTES, 2)).hasSize(2); + } + @Test // DATAREDIS-315 public void zRangeByLexShouldReturnResultCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java index 2667d8103..13d523979 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java @@ -27,6 +27,7 @@ import java.util.Collections; import java.util.List; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.springframework.data.domain.Range; import org.springframework.data.redis.ByteBufferObjectFactory; @@ -142,6 +143,47 @@ public class DefaultReactiveZSetOperationsIntegrationTests { zSetOperations.incrementScore(key, value, 1.1).as(StepVerifier::create).expectNext(43.2).verifyComplete(); } + @ParameterizedRedisTest // GH-2049 + @EnabledOnCommand("ZRANDMEMBER") + void randomMember() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + zSetOperations.add(key, value1, 42.1).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, value2, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.randomMember(key).as(StepVerifier::create).consumeNextWith(actual -> { + + assertThat(actual).isIn(value1, value2); + }).verifyComplete(); + + zSetOperations.randomMembers(key, 2).as(StepVerifier::create).expectNextCount(2).verifyComplete(); + zSetOperations.distinctRandomMembers(key, 2).as(StepVerifier::create).expectNextCount(2).verifyComplete(); + } + + @ParameterizedRedisTest // GH-2049 + @Disabled("https://github.com/redis/redis/issues/9160") + @EnabledOnCommand("ZRANDMEMBER") + void randomMemberWithScore() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + zSetOperations.add(key, value1, 42.1).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, value2, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.randomMemberWithScore(key).as(StepVerifier::create).consumeNextWith(actual -> { + + assertThat(actual).isIn(new DefaultTypedTuple<>(value1, 42.1d), new DefaultTypedTuple<>(value2, 10d)); + }).verifyComplete(); + + zSetOperations.randomMembersWithScore(key, 2).as(StepVerifier::create).expectNextCount(2).verifyComplete(); + zSetOperations.distinctRandomMembersWithScore(key, 2).as(StepVerifier::create).expectNextCount(2).verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-602 void rank() { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java index 600337cb4..551b26b2f 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java @@ -27,12 +27,14 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.springframework.data.redis.DoubleAsStringObjectFactory; import org.springframework.data.redis.DoubleObjectFactory; import org.springframework.data.redis.LongAsStringObjectFactory; import org.springframework.data.redis.LongObjectFactory; import org.springframework.data.redis.ObjectFactory; +import org.springframework.data.redis.RawObjectFactory; import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.connection.RedisZSetCommands.Weights; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; @@ -187,6 +189,49 @@ public class DefaultZSetOperationsIntegrationTests { assertThat(tuple).isEqualTo(new DefaultTypedTuple<>(value1, 5.7)); } + @ParameterizedRedisTest // GH-2049 + @EnabledOnCommand("ZRANDMEMBER") + void testRandomMember() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + + zSetOps.add(key, value1, 1.9); + zSetOps.add(key, value2, 3.7); + zSetOps.add(key, value3, 5.8); + + assertThat(zSetOps.randomMember(key)).isIn(value1, value2, value3); + assertThat(zSetOps.randomMembers(key, 2)).hasSize(2).containsAnyOf(value1, value2, value3); + assertThat(zSetOps.distinctRandomMembers(key, 2)).hasSize(2).containsAnyOf(value1, value2, value3); + } + + @ParameterizedRedisTest // GH-2049 + @Disabled("https://github.com/redis/redis/issues/9160") + @EnabledOnCommand("ZRANDMEMBER") + void testRandomMemberWithScore() { + + assumeThat(valueFactory).isNotInstanceOf(RawObjectFactory.class); + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + + zSetOps.add(key, value1, 1.9); + zSetOps.add(key, value2, 3.7); + zSetOps.add(key, value3, 5.8); + + assertThat(zSetOps.randomMemberWithScore(key)).isIn(new DefaultTypedTuple<>(value1, 1.9d), + new DefaultTypedTuple<>(value2, 3.7d), new DefaultTypedTuple<>(value3, 5.8d)); + assertThat(zSetOps.randomMembersWithScore(key, 2)).hasSize(2).containsAnyOf(new DefaultTypedTuple<>(value1, 1.9d), + new DefaultTypedTuple<>(value2, 3.7d), new DefaultTypedTuple<>(value3, 5.8d)); + assertThat(zSetOps.distinctRandomMembersWithScore(key, 2)).hasSize(2).containsAnyOf( + new DefaultTypedTuple<>(value1, 1.9d), new DefaultTypedTuple<>(value2, 3.7d), + new DefaultTypedTuple<>(value3, 5.8d)); + } + @ParameterizedRedisTest void testRangeByScoreOffsetCount() {