Add support for HRANDFIELD.

Closes: #2048
Original Pull Request: #2104
This commit is contained in:
Mark Paluch
2021-06-25 11:55:14 +02:00
committed by Christoph Strobl
parent 961ba9035d
commit 0b8fbae2c3
22 changed files with 1125 additions and 19 deletions

View File

@@ -106,6 +106,26 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
@SuppressWarnings("rawtypes") private Queue<Converter> txConverters = new LinkedList<>();
private boolean deserializePipelineAndTxResults = false;
private Entry<String, String> convertEntry(Entry<byte[], byte[]> source) {
return new Entry<String, String>() {
@Override
public String getKey() {
return bytesToString.convert(source.getKey());
}
@Override
public String getValue() {
return bytesToString.convert(source.getValue());
}
@Override
public String setValue(String value) {
throw new UnsupportedOperationException("Cannot set value for entry");
}
};
}
private class DeserializingConverter implements Converter<byte[], String> {
public String convert(byte[] source) {
return serializer.deserialize(source);
@@ -2307,6 +2327,88 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return hIncrBy(serialize(key), serialize(field), delta);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandField(byte[])
*/
@Nullable
@Override
public byte[] hRandField(byte[] key) {
return convertAndReturn(delegate.hRandField(key), Converters.identityConverter());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandFieldWithValues(byte[])
*/
@Nullable
@Override
public Entry<byte[], byte[]> hRandFieldWithValues(byte[] key) {
return convertAndReturn(delegate.hRandFieldWithValues(key), Converters.identityConverter());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandField(byte[], long)
*/
@Nullable
@Override
public List<byte[]> hRandField(byte[] key, long count) {
return convertAndReturn(delegate.hRandField(key, count), Converters.identityConverter());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandFieldWithValues(byte[], long)
*/
@Nullable
@Override
public List<Entry<byte[], byte[]>> hRandFieldWithValues(byte[] key, long count) {
return convertAndReturn(delegate.hRandFieldWithValues(key, count), Converters.identityConverter());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#hRandField(java.lang.String)
*/
@Nullable
@Override
public String hRandField(String key) {
return convertAndReturn(delegate.hRandField(serialize(key)), bytesToString);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#hRandFieldWithValues(java.lang.String)
*/
@Nullable
@Override
public Entry<String, String> hRandFieldWithValues(String key) {
return convertAndReturn(delegate.hRandFieldWithValues(serialize(key)),
(Converter<Entry<byte[], byte[]>, Entry<String, String>>) this::convertEntry);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#hRandField(java.lang.String, long)
*/
@Nullable
@Override
public List<String> hRandField(String key, long count) {
return convertAndReturn(delegate.hRandField(serialize(key), count), byteListToStringList);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#hRandFieldWithValues(java.lang.String, long)
*/
@Nullable
@Override
public List<Entry<String, String>> hRandFieldWithValues(String key, long count) {
return convertAndReturn(delegate.hRandFieldWithValues(serialize(key), count),
new ListConverter<>(this::convertEntry));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#hKeys(java.lang.String)
@@ -3924,24 +4026,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
@Override
public Cursor<Entry<String, String>> hScan(String key, ScanOptions options) {
return new ConvertingCursor<>(this.delegate.hScan(this.serialize(key), options),
source -> new Entry<String, String>() {
@Override
public String getKey() {
return bytesToString.convert(source.getKey());
}
@Override
public String getValue() {
return bytesToString.convert(source.getValue());
}
@Override
public String setValue(String value) {
throw new UnsupportedOperationException("Cannot set value for entry in cursor");
}
});
return new ConvertingCursor<>(this.delegate.hScan(this.serialize(key), options), this::convertEntry);
}
/*

View File

@@ -1329,6 +1329,34 @@ public interface DefaultedRedisConnection extends RedisConnection {
return hashCommands().hIncrBy(key, field, delta);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated
default byte[] hRandField(byte[] key) {
return hashCommands().hRandField(key);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated
default Entry<byte[], byte[]> hRandFieldWithValues(byte[] key) {
return hashCommands().hRandFieldWithValues(key);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated
default List<byte[]> hRandField(byte[] key, long count) {
return hashCommands().hRandField(key, count);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated
default List<Entry<byte[], byte[]>> hRandFieldWithValues(byte[] key, long count) {
return hashCommands().hRandFieldWithValues(key, count);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated

View File

@@ -512,6 +512,145 @@ public interface ReactiveHashCommands {
*/
Flux<NumericResponse<KeyCommand, Long>> hLen(Publisher<KeyCommand> commands);
/**
* {@literal HRANDFIELD} {@link Command}.
*
* @author Mark Paluch
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
class HRandFieldCommand extends KeyCommand {
private long count;
private HRandFieldCommand(@Nullable ByteBuffer key, long count) {
super(key);
this.count = count;
}
/**
* Applies the hash {@literal key}. Constructs a new command instance with all previously configured properties.
*
* @param key must not be {@literal null}.
* @return a new {@link HRandFieldCommand} with {@literal key} applied.
*/
public static HRandFieldCommand key(ByteBuffer key) {
Assert.notNull(key, "Key must not be null!");
return new HRandFieldCommand(key, 1);
}
/**
* Applies the {@literal count}. Constructs a new command instance with all previously configured properties. If the
* provided {@code count} argument is positive, return a list of distinct fields, capped either at {@code count} or
* the hash size. If {@code count} is negative, the behavior changes and the command is allowed to return the same
* field multiple times. In this case, the number of returned fields is the absolute value of the specified count.
*
* @param count
* @return a new {@link HRandFieldCommand} with {@literal key} applied.
*/
public HRandFieldCommand count(long count) {
return new HRandFieldCommand(getKey(), count);
}
public long getCount() {
return count;
}
}
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
default Mono<ByteBuffer> hRandField(ByteBuffer key) {
Assert.notNull(key, "Key must not be null!");
return hRandField(Mono.just(HRandFieldCommand.key(key).count(1))).flatMap(CommandResponse::getOutput).next();
}
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
default Mono<Map.Entry<ByteBuffer, ByteBuffer>> hRandFieldWithValues(ByteBuffer key) {
Assert.notNull(key, "Key must not be null!");
return hRandFieldWithValues(Mono.just(HRandFieldCommand.key(key).count(1))).flatMap(CommandResponse::getOutput)
.next();
}
/**
* Return a random field from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param key must not be {@literal null}.
* @param count number of fields to return.
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
default Flux<ByteBuffer> hRandField(ByteBuffer key, long count) {
Assert.notNull(key, "Key must not be null!");
return hRandField(Mono.just(HRandFieldCommand.key(key).count(count))).flatMap(CommandResponse::getOutput);
}
/**
* Return a random field from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param key must not be {@literal null}.
* @param count number of fields to return.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
default Flux<Map.Entry<ByteBuffer, ByteBuffer>> hRandFieldWithValues(ByteBuffer key, long count) {
Assert.notNull(key, "Key must not be null!");
return hRandFieldWithValues(Mono.just(HRandFieldCommand.key(key).count(count))).flatMap(CommandResponse::getOutput);
}
/**
* Get random fields of hash at {@literal key}.
*
* @param commands must not be {@literal null}.
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
Flux<CommandResponse<HRandFieldCommand, Flux<ByteBuffer>>> hRandField(Publisher<HRandFieldCommand> commands);
/**
* Get random fields along their values of hash at {@literal key}.
*
* @param commands must not be {@literal null}.
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
Flux<CommandResponse<HRandFieldCommand, Flux<Map.Entry<ByteBuffer, ByteBuffer>>>> hRandFieldWithValues(
Publisher<HRandFieldCommand> commands);
/**
* Get key set (fields) of hash at {@literal key}.
*

View File

@@ -173,6 +173,58 @@ public interface RedisHashCommands {
@Nullable
Map<byte[], byte[]> hGetAll(byte[] key);
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
byte[] hRandField(byte[] key);
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
Map.Entry<byte[], byte[]> hRandFieldWithValues(byte[] key);
/**
* Return a random field from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param key must not be {@literal null}.
* @param count number of fields to return.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
List<byte[]> hRandField(byte[] key, long count);
/**
* Return a random field from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param key must not be {@literal null}.
* @param count number of fields to return.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
List<Map.Entry<byte[], byte[]>> hRandFieldWithValues(byte[] key, long count);
/**
* Use a {@link Cursor} to iterate over entries in hash at {@code key}.
*

View File

@@ -1964,6 +1964,58 @@ public interface StringRedisConnection extends RedisConnection {
*/
Double hIncrBy(String key, String field, double delta);
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
String hRandField(String key);
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
Map.Entry<String, String> hRandFieldWithValues(String key);
/**
* Return a random field from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param key must not be {@literal null}.
* @param count number of fields to return.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
List<String> hRandField(String key, long count);
/**
* Return a random field from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param key must not be {@literal null}.
* @param count number of fields to return.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
List<Map.Entry<String, String>> hRandFieldWithValues(String key, long count);
/**
* Determine if given hash {@code field} exists.
*

View File

@@ -461,6 +461,20 @@ abstract public class Converters {
return source;
}
/**
* Create an {@link Map.Entry} from {@code key} and {@code value}.
*
* @param key
* @param value
* @param <K>
* @param <V>
* @return
* @since 2.6
*/
public static <K, V> Map.Entry<K, V> entryOf(K key, V value) {
return new SimpleEntry<>(key, value);
}
/**
* @author Christoph Strobl
* @since 1.8
@@ -639,4 +653,30 @@ abstract public class Converters {
}
}
private static class SimpleEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private final V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -30,6 +30,7 @@ import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -167,6 +168,74 @@ class JedisClusterHashCommands implements RedisHashCommands {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandField(byte[])
*/
@Nullable
@Override
public byte[] hRandField(byte[] key) {
Assert.notNull(key, "Key must not be null!");
try {
return connection.getCluster().hrandfield(key);
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandFieldWithValues(byte[])
*/
@Nullable
@Override
public Entry<byte[], byte[]> hRandFieldWithValues(byte[] key) {
Assert.notNull(key, "Key must not be null!");
try {
Map<byte[], byte[]> map = connection.getCluster().hrandfieldWithValues(key, 1);
return map.isEmpty() ? null : map.entrySet().iterator().next();
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandField(byte[], long)
*/
@Nullable
@Override
public List<byte[]> hRandField(byte[] key, long count) {
Assert.notNull(key, "Key must not be null!");
try {
return connection.getCluster().hrandfield(key, count);
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandFieldWithValues(byte[], long)
*/
@Nullable
@Override
public List<Entry<byte[], byte[]>> hRandFieldWithValues(byte[] key, long count) {
try {
Map<byte[], byte[]> map = connection.getCluster().hrandfieldWithValues(key, count);
return Streamable.of(() -> map.entrySet().iterator()).toList();
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hExists(byte[], byte[])

View File

@@ -20,12 +20,14 @@ import redis.clients.jedis.MultiKeyPipelineBase;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.data.redis.connection.RedisHashCommands;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.KeyBoundCursor;
import org.springframework.data.redis.core.ScanIteration;
@@ -127,6 +129,67 @@ class JedisHashCommands implements RedisHashCommands {
return connection.invoke().just(BinaryJedis::hgetAll, MultiKeyPipelineBase::hgetAll, key);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandField(byte[])
*/
@Nullable
@Override
public byte[] hRandField(byte[] key) {
Assert.notNull(key, "Key must not be null!");
return connection.invoke().just(BinaryJedis::hrandfield, MultiKeyPipelineBase::hrandfield, key);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandFieldWithValues(byte[])
*/
@Nullable
@Override
public Entry<byte[], byte[]> hRandFieldWithValues(byte[] key) {
Assert.notNull(key, "Key must not be null!");
return connection.invoke()
.from(BinaryJedis::hrandfieldWithValues, MultiKeyPipelineBase::hrandfieldWithValues, key, 1L)
.get(it -> it.isEmpty() ? null : it.entrySet().iterator().next());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandField(byte[], long)
*/
@Nullable
@Override
public List<byte[]> hRandField(byte[] key, long count) {
Assert.notNull(key, "Key must not be null!");
return connection.invoke().just(BinaryJedis::hrandfield, MultiKeyPipelineBase::hrandfield, key, count);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandFieldWithValues(byte[], long)
*/
@Nullable
@Override
public List<Entry<byte[], byte[]>> hRandFieldWithValues(byte[] key, long count) {
Assert.notNull(key, "Key must not be null!");
return connection.invoke()
.from(BinaryJedis::hrandfieldWithValues, MultiKeyPipelineBase::hrandfieldWithValues, key, count).get(it -> {
List<Entry<byte[], byte[]>> entries = new ArrayList<>(it.size());
it.forEach((k, v) -> entries.add(Converters.entryOf(k, v)));
return entries;
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hIncrBy(byte[], byte[], long)
@@ -280,4 +343,5 @@ class JedisHashCommands implements RedisHashCommands {
private boolean isQueueing() {
return connection.isQueueing();
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.connection.lettuce;
import io.lettuce.core.KeyValue;
import io.lettuce.core.MapScanCursor;
import io.lettuce.core.ScanArgs;
import io.lettuce.core.api.async.RedisHashAsyncCommands;
@@ -25,6 +26,7 @@ import java.util.Map.Entry;
import java.util.Set;
import org.springframework.data.redis.connection.RedisHashCommands;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.KeyBoundCursor;
import org.springframework.data.redis.core.ScanIteration;
@@ -124,6 +126,60 @@ class LettuceHashCommands implements RedisHashCommands {
return connection.invoke().just(RedisHashAsyncCommands::hgetall, key);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandField(byte[])
*/
@Nullable
@Override
public byte[] hRandField(byte[] key) {
Assert.notNull(key, "Key must not be null!");
return connection.invoke().just(RedisHashAsyncCommands::hrandfield, key);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandFieldWithValues(byte[])
*/
@Nullable
@Override
public Entry<byte[], byte[]> hRandFieldWithValues(byte[] key) {
Assert.notNull(key, "Key must not be null!");
return connection.invoke().from(RedisHashAsyncCommands::hrandfieldWithvalues, key)
.get(LettuceHashCommands::toEntry);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandField(byte[], long)
*/
@Nullable
@Override
public List<byte[]> hRandField(byte[] key, long count) {
Assert.notNull(key, "Key must not be null!");
return connection.invoke().just(RedisHashAsyncCommands::hrandfield, key, count);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hRandFieldWithValues(byte[], long)
*/
@Nullable
@Override
public List<Entry<byte[], byte[]>> hRandFieldWithValues(byte[] key, long count) {
Assert.notNull(key, "Key must not be null!");
return connection.invoke().fromMany(RedisHashAsyncCommands::hrandfieldWithvalues, key, count)
.toList(LettuceHashCommands::toEntry);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hIncrBy(byte[], byte[], long)
@@ -275,4 +331,9 @@ class LettuceHashCommands implements RedisHashCommands {
return connection.invoke().just(RedisHashAsyncCommands::hstrlen, key, field);
}
@Nullable
private static Entry<byte[], byte[]> toEntry(KeyValue<byte[], byte[]> value) {
return value.hasValue() ? Converters.entryOf(value.getKey(), value.getValue()) : null;
}
}

View File

@@ -36,6 +36,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyComm
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScanCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.util.Assert;
/**
@@ -164,6 +165,39 @@ class LettuceReactiveHashCommands implements ReactiveHashCommands {
}));
}
@Override
public Flux<CommandResponse<HRandFieldCommand, Flux<ByteBuffer>>> hRandField(Publisher<HRandFieldCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).map(command -> {
Assert.notNull(command.getKey(), "Command.getKey() must not be null!");
return new CommandResponse<>(command, cmd.hrandfield(command.getKey(), command.getCount()));
}));
}
@Override
public Flux<CommandResponse<HRandFieldCommand, Flux<Entry<ByteBuffer, ByteBuffer>>>> hRandFieldWithValues(
Publisher<HRandFieldCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).map(command -> {
Assert.notNull(command.getKey(), "Command.getKey() must not be null!");
Flux<Entry<ByteBuffer, ByteBuffer>> flux = cmd.hrandfieldWithvalues(command.getKey(), command.getCount())
.handle((it, sink) -> {
if (it.isEmpty()) {
return;
}
sink.next(Converters.entryOf(it.getKey(), it.getValue()));
});
return new CommandResponse<>(command, flux);
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hKeys(org.reactivestreams.Publisher)

View File

@@ -282,6 +282,14 @@ abstract class AbstractOperations<K, V> {
return SerializationUtils.deserialize(rawKeys, hashKeySerializer());
}
@SuppressWarnings("unchecked")
<T> List<T> deserializeHashKeys(List<byte[]> rawKeys) {
if (hashKeySerializer() == null) {
return (List<T>) rawKeys;
}
return SerializationUtils.deserialize(rawKeys, hashKeySerializer());
}
@SuppressWarnings("unchecked")
<T> List<T> deserializeHashValues(List<byte[]> rawValues) {
if (hashValueSerializer() == null) {

View File

@@ -88,6 +88,51 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
@Nullable
Double increment(HK key, double delta);
/**
* Return a random field from the hash value stored at the bound key.
*
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
HK randomField();
/**
* Return a random field from the hash value stored at the bound key.
*
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
Map.Entry<HK, HV> randomValue();
/**
* Return a random field from the hash value stored at the bound key. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param count number of fields to return.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
List<HK> randomFields(long count);
/**
* Return a random field from the hash value stored at the bound key.
*
* @param count number of fields to return. Must be positive.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
Map<HK, HV> randomValues(long count);
/**
* Get key set (fields) of hash at the bound key.
*

View File

@@ -110,6 +110,46 @@ class DefaultBoundHashOperations<H, HK, HV> extends DefaultBoundKeyOperations<H>
return ops.increment(getKey(), key, delta);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundHashOperations#randomField()
*/
@Nullable
@Override
public HK randomField() {
return ops.randomField(getKey());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundHashOperations#randomValue()
*/
@Nullable
@Override
public Entry<HK, HV> randomValue() {
return ops.randomValue(getKey());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundHashOperations#randomFields(long)
*/
@Nullable
@Override
public List<HK> randomFields(long count) {
return ops.randomFields(getKey(), count);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundHashOperations#randomValues(long)
*/
@Nullable
@Override
public Map<HK, HV> randomValues(long count) {
return ops.randomValues(getKey(), count);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundHashOperations#keys()

View File

@@ -24,7 +24,9 @@ import java.util.Map.Entry;
import java.util.Set;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Default implementation of {@link HashOperations}.
@@ -91,6 +93,67 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
return execute(connection -> connection.hIncrBy(rawKey, rawHashKey, delta), true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.HashOperations#randomField(java.lang.Object)
*/
@Nullable
@Override
public HK randomField(K key) {
byte[] rawKey = rawKey(key);
return deserializeHashKey(execute(connection -> connection.hRandField(rawKey), true));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.HashOperations#randomValue(java.lang.Object)
*/
@Nullable
@Override
public Entry<HK, HV> randomValue(K key) {
byte[] rawKey = rawKey(key);
Entry<byte[], byte[]> rawEntry = execute(connection -> connection.hRandFieldWithValues(rawKey), true);
return rawEntry == null ? null
: Converters.entryOf(deserializeHashKey(rawEntry.getKey()), deserializeHashValue(rawEntry.getValue()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.HashOperations#randomFields(java.lang.Object, long)
*/
@Nullable
@Override
public List<HK> randomFields(K key, long count) {
byte[] rawKey = rawKey(key);
List<byte[]> rawValues = execute(connection -> connection.hRandField(rawKey, count), true);
return deserializeHashKeys(rawValues);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.HashOperations#randomValues(java.lang.Object, long)
*/
@Nullable
@Override
public Map<HK, HV> randomValues(K key, long count) {
Assert.isTrue(count > 0, "Count must not be negative");
byte[] rawKey = rawKey(key);
List<Entry<byte[], byte[]>> rawEntries = execute(connection -> connection.hRandFieldWithValues(rawKey, count),
true);
if (rawEntries == null) {
return null;
}
Map<byte[], byte[]> rawMap = new LinkedHashMap<>(rawEntries.size());
rawEntries.forEach(entry -> rawMap.put(entry.getKey(), entry.getValue()));
return deserializeHashMap(rawMap);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.HashOperations#keys(java.lang.Object)

View File

@@ -144,6 +144,58 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
.hIncrBy(rawKey(key), rawHashKey(hashKey), delta));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveHashOperations#randomField(H)
*/
@Override
public Mono<HK> randomField(H key) {
Assert.notNull(key, "Key must not be null!");
return template.createMono(connection -> connection //
.hashCommands().hRandField(rawKey(key))).map(this::readHashKey);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveHashOperations#randomValue(H)
*/
@Override
public Mono<Map.Entry<HK, HV>> randomValue(H key) {
Assert.notNull(key, "Key must not be null!");
return template.createMono(connection -> connection //
.hashCommands().hRandFieldWithValues(rawKey(key))).map(this::deserializeHashEntry);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveHashOperations#randomFields(H, long)
*/
@Override
public Flux<HK> randomFields(H key, long count) {
Assert.notNull(key, "Key must not be null!");
return template.createFlux(connection -> connection //
.hashCommands().hRandField(rawKey(key), count)).map(this::readHashKey);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveHashOperations#randomValues(H, long)
*/
@Override
public Flux<Map.Entry<HK, HV>> randomValues(H key, long count) {
Assert.notNull(key, "Key must not be null!");
return template.createFlux(connection -> connection //
.hashCommands().hRandFieldWithValues(rawKey(key), count)).map(this::deserializeHashEntry);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveHashOperations#keys(java.lang.Object)

View File

@@ -88,6 +88,55 @@ public interface HashOperations<H, HK, HV> {
*/
Double increment(H key, HK hashKey, double delta);
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
HK randomField(H key);
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
Map.Entry<HK, HV> randomValue(H key);
/**
* Return a random field from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param key must not be {@literal null}.
* @param count number of fields to return.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
List<HK> randomFields(H key, long count);
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @param count number of fields to return. Must be positive.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
Map<HK, HV> randomValues(H key, long count);
/**
* Get key set (fields) of hash at {@code key}.
*

View File

@@ -87,6 +87,54 @@ public interface ReactiveHashOperations<H, HK, HV> {
*/
Mono<Double> increment(H key, HK hashKey, double delta);
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
Mono<HK> randomField(H key);
/**
* Return a random field from the hash value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
Mono<Map.Entry<HK, HV>> randomValue(H key);
/**
* Return a random field from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param key must not be {@literal null}.
* @param count number of fields to return.
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
Flux<HK> randomFields(H key, long count);
/**
* Return a random field from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct fields, capped either at {@code count} or the hash size. If {@code count} is
* negative, the behavior changes and the command is allowed to return the same field multiple times. In this case,
* the number of returned fields is the absolute value of the specified count.
*
* @param key must not be {@literal null}.
* @param count number of fields to return.
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
Flux<Map.Entry<HK, HV>> randomValues(H key, long count);
/**
* Get key set (fields) of hash at {@code key}.
*