committed by
Christoph Strobl
parent
961ba9035d
commit
0b8fbae2c3
@@ -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`).
|
||||
* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`, `COPY`, `GETEX`, `GETDEL`, `ZPOPMIN`, `BZPOPMIN`, `ZPOPMAX`, `BZPOPMAX`, `ZMSCORE`, `ZDIFF`, `ZDIFFSTORE`, `ZINTER`, `ZUNION`, `HRANDFIELD`).
|
||||
|
||||
[[new-in-2.5.0]]
|
||||
== New in Spring Data Redis 2.5
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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[])
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -1269,6 +1269,52 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
verifyResults(Arrays.asList(new Object[] { true, largeNumber, -largeNumber }));
|
||||
}
|
||||
|
||||
@Test // GH-2048
|
||||
@EnabledOnCommand("HRANDFIELD")
|
||||
void testHRandField() {
|
||||
|
||||
String key = "hash";
|
||||
Map<String, String> hash = new HashMap<>();
|
||||
hash.put("key1", "val1");
|
||||
hash.put("key2", "val2");
|
||||
hash.put("key3", "val3");
|
||||
hash.put("key4", "val4");
|
||||
|
||||
connection.hMSet(key, hash);
|
||||
actual.add(connection.hRandField(key));
|
||||
actual.add(connection.hRandField(key, 2));
|
||||
actual.add(connection.hRandField(key, -10));
|
||||
|
||||
List<Object> results = getResults();
|
||||
assertThat((String) results.get(0)).isIn(hash.keySet());
|
||||
assertThat((List<String>) results.get(1)).hasSize(2);
|
||||
assertThat((List<String>) results.get(2)).hasSize(10);
|
||||
}
|
||||
|
||||
@Test // GH-2048
|
||||
@EnabledOnCommand("HRANDFIELD")
|
||||
void testHRandFieldWithValues() {
|
||||
|
||||
String key = "hash";
|
||||
Map<String, String> hash = new HashMap<>();
|
||||
hash.put("key1", "val1");
|
||||
hash.put("key2", "val2");
|
||||
hash.put("key3", "val3");
|
||||
hash.put("key4", "val4");
|
||||
|
||||
connection.hMSet(key, hash);
|
||||
actual.add(connection.hRandFieldWithValues(key));
|
||||
actual.add(connection.hRandFieldWithValues(key, 2));
|
||||
actual.add(connection.hRandFieldWithValues(key, -10));
|
||||
|
||||
List<Object> results = getResults();
|
||||
assertThat(results.get(0)).isNotNull();
|
||||
assertThat((List<?>) results.get(1)).hasSize(2);
|
||||
|
||||
// Oh Jedis, JedisByteHashMap.ByteArrayWrapper. Why?
|
||||
assertThat((List<?>) results.get(2)).hasSizeGreaterThan(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIncDecr() {
|
||||
|
||||
|
||||
@@ -657,6 +657,22 @@ class RedisConnectionUnitTests {
|
||||
return delegate.hGetAll(key);
|
||||
}
|
||||
|
||||
public byte[] hRandField(byte[] key) {
|
||||
return delegate.hRandField(key);
|
||||
}
|
||||
|
||||
public Entry<byte[], byte[]> hRandFieldWithValues(byte[] key) {
|
||||
return delegate.hRandFieldWithValues(key);
|
||||
}
|
||||
|
||||
public List<byte[]> hRandField(byte[] key, long count) {
|
||||
return delegate.hRandField(key, count);
|
||||
}
|
||||
|
||||
public List<Entry<byte[], byte[]>> hRandFieldWithValues(byte[] key, long count) {
|
||||
return delegate.hRandFieldWithValues(key, count);
|
||||
}
|
||||
|
||||
public Boolean move(byte[] key, int dbIndex) {
|
||||
return delegate.move(key, dbIndex);
|
||||
}
|
||||
|
||||
@@ -162,4 +162,43 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
|
||||
assertThat(hashOps.lengthOfValue(key, key1)).isEqualTo(Long.valueOf(val1.toString().length()));
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest // GH-2048
|
||||
void randomField() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
HV val1 = hashValueFactory.instance();
|
||||
HK key2 = hashKeyFactory.instance();
|
||||
HV val2 = hashValueFactory.instance();
|
||||
hashOps.put(key, key1, val1);
|
||||
hashOps.put(key, key2, val2);
|
||||
|
||||
assertThat(hashOps.randomField(key)).isIn(key1, key2);
|
||||
assertThat(hashOps.randomFields(key, 2)).hasSize(2).contains(key1, key2);
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest // GH-2048
|
||||
void randomValue() {
|
||||
|
||||
assumeThat(hashKeyFactory).isNotInstanceOf(RawObjectFactory.class);
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
HV val1 = hashValueFactory.instance();
|
||||
HK key2 = hashKeyFactory.instance();
|
||||
HV val2 = hashValueFactory.instance();
|
||||
hashOps.put(key, key1, val1);
|
||||
hashOps.put(key, key2, val2);
|
||||
|
||||
Map.Entry<HK, HV> entry = hashOps.randomValue(key);
|
||||
|
||||
if (entry.getKey().equals(key1)) {
|
||||
assertThat(entry.getValue()).isEqualTo(val1);
|
||||
} else {
|
||||
assertThat(entry.getValue()).isEqualTo(val2);
|
||||
}
|
||||
|
||||
Map<HK, HV> values = hashOps.randomValues(key, 10);
|
||||
assertThat(values).hasSize(2).containsEntry(key1, val1).containsEntry(key2, val2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.data.redis.test.condition.EnabledOnCommand;
|
||||
import org.springframework.data.redis.test.extension.LettuceTestClientResources;
|
||||
import org.springframework.data.redis.test.extension.parametrized.MethodSource;
|
||||
import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest;
|
||||
@@ -228,6 +229,81 @@ public class DefaultReactiveHashOperationsIntegrationTests<K, HK, HV> {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest // GH-2048
|
||||
@EnabledOnCommand("HRANDFIELD")
|
||||
void randomField() {
|
||||
|
||||
assumeThat(hashValueFactory).isNotInstanceOf(RawObjectFactory.class);
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK hashkey1 = hashKeyFactory.instance();
|
||||
HV hashvalue1 = hashValueFactory.instance();
|
||||
HK hashkey2 = hashKeyFactory.instance();
|
||||
HV hashvalue2 = hashValueFactory.instance();
|
||||
|
||||
hashOperations.put(key, hashkey1, hashvalue1) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
|
||||
hashOperations.put(key, hashkey2, hashvalue2) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
|
||||
hashOperations.randomField(key) //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(actual -> {
|
||||
assertThat(actual).isIn(hashkey1, hashkey2);
|
||||
}).verifyComplete();
|
||||
|
||||
hashOperations.randomFields(key, -10) //
|
||||
.collectList().as(StepVerifier::create) //
|
||||
.assertNext(actual -> {
|
||||
assertThat(actual).hasSize(10);
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest // GH-2048
|
||||
@EnabledOnCommand("HRANDFIELD")
|
||||
void randomValue() {
|
||||
|
||||
assumeThat(hashValueFactory).isNotInstanceOf(RawObjectFactory.class);
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK hashkey1 = hashKeyFactory.instance();
|
||||
HV hashvalue1 = hashValueFactory.instance();
|
||||
HK hashkey2 = hashKeyFactory.instance();
|
||||
HV hashvalue2 = hashValueFactory.instance();
|
||||
|
||||
hashOperations.put(key, hashkey1, hashvalue1) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
|
||||
hashOperations.put(key, hashkey2, hashvalue2) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
|
||||
hashOperations.randomValue(key) //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(actual -> {
|
||||
|
||||
if (actual.getKey().equals(hashkey1)) {
|
||||
assertThat(actual.getValue()).isEqualTo(hashvalue1);
|
||||
} else {
|
||||
assertThat(actual.getValue()).isEqualTo(hashvalue2);
|
||||
}
|
||||
}).verifyComplete();
|
||||
|
||||
hashOperations.randomValues(key, -10) //
|
||||
.collectList().as(StepVerifier::create) //
|
||||
.assertNext(actual -> {
|
||||
assertThat(actual).hasSize(10);
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest // DATAREDIS-602
|
||||
@SuppressWarnings("unchecked")
|
||||
void incrementDouble() {
|
||||
|
||||
Reference in New Issue
Block a user