Support HRANDFIELD command via RedisMap.

Add randomKey() and randomEntry() to RedisHash.
Update documentation and align methods with java terminology around keys vs. fields in hashes.
Move Converters off their custom Map.Entry implementation and use SimpleImmutableEntry instead.
Switch code that creates Map.Entry to the newly introduced Converters.entryOf method.

See: #2048
Original Pull Request: #2104
This commit is contained in:
Christoph Strobl
2021-06-30 11:11:36 +02:00
parent a523b821c5
commit 4c4c96e9cf
15 changed files with 185 additions and 144 deletions

View File

@@ -108,23 +108,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
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");
}
};
return Converters.entryOf(bytesToString.convert(source.getKey()), bytesToString.convert(source.getValue()));
}
private class DeserializingConverter implements Converter<byte[], String> {

View File

@@ -18,16 +18,7 @@ package org.springframework.data.redis.connection.convert;
import java.io.StringReader;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
@@ -472,7 +463,7 @@ abstract public class Converters {
* @since 2.6
*/
public static <K, V> Map.Entry<K, V> entryOf(K key, V value) {
return new SimpleEntry<>(key, value);
return new AbstractMap.SimpleImmutableEntry<>(key, value);
}
/**
@@ -653,30 +644,4 @@ 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

@@ -89,49 +89,49 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
Double increment(HK key, double delta);
/**
* Return a random field from the hash value stored at the bound key.
* Return a random key (aka field) from the hash value stored at the bound key.
*
* @return {@literal null} if key does not exist or when used in pipeline / transaction.
* @return {@literal null} if the hash 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();
HK randomKey();
/**
* Return a random field from the hash value stored at the bound key.
* Return a random entry 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();
Map.Entry<HK, HV> randomEntry();
/**
* 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.
* Return a random keys (aka fields) from the hash value stored at the bound key. If the provided {@code count} argument is
* positive, return a list of distinct keys, 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 key multiple times. In this case,
* the number of returned keys is the absolute value of the specified count.
*
* @param count number of fields to return.
* @param count number of keys 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);
List<HK> randomKeys(long count);
/**
* Return a random field from the hash value stored at the bound key.
* Return a random entry 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.
* @param count number of entries to return. Must be positive.
* @return {@literal null} if the hash 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);
Map<HK, HV> randomEntries(long count);
/**
* Get key set (fields) of hash at the bound key.

View File

@@ -116,8 +116,8 @@ class DefaultBoundHashOperations<H, HK, HV> extends DefaultBoundKeyOperations<H>
*/
@Nullable
@Override
public HK randomField() {
return ops.randomField(getKey());
public HK randomKey() {
return ops.randomKey(getKey());
}
/*
@@ -126,8 +126,8 @@ class DefaultBoundHashOperations<H, HK, HV> extends DefaultBoundKeyOperations<H>
*/
@Nullable
@Override
public Entry<HK, HV> randomValue() {
return ops.randomValue(getKey());
public Entry<HK, HV> randomEntry() {
return ops.randomEntry(getKey());
}
/*
@@ -136,8 +136,8 @@ class DefaultBoundHashOperations<H, HK, HV> extends DefaultBoundKeyOperations<H>
*/
@Nullable
@Override
public List<HK> randomFields(long count) {
return ops.randomFields(getKey(), count);
public List<HK> randomKeys(long count) {
return ops.randomKeys(getKey(), count);
}
/*
@@ -146,8 +146,8 @@ class DefaultBoundHashOperations<H, HK, HV> extends DefaultBoundKeyOperations<H>
*/
@Nullable
@Override
public Map<HK, HV> randomValues(long count) {
return ops.randomValues(getKey(), count);
public Map<HK, HV> randomEntries(long count) {
return ops.randomEntries(getKey(), count);
}
/*

View File

@@ -99,7 +99,7 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
*/
@Nullable
@Override
public HK randomField(K key) {
public HK randomKey(K key) {
byte[] rawKey = rawKey(key);
return deserializeHashKey(execute(connection -> connection.hRandField(rawKey), true));
@@ -111,7 +111,7 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
*/
@Nullable
@Override
public Entry<HK, HV> randomValue(K key) {
public Entry<HK, HV> randomEntry(K key) {
byte[] rawKey = rawKey(key);
Entry<byte[], byte[]> rawEntry = execute(connection -> connection.hRandFieldWithValues(rawKey), true);
@@ -125,7 +125,7 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
*/
@Nullable
@Override
public List<HK> randomFields(K key, long count) {
public List<HK> randomKeys(K key, long count) {
byte[] rawKey = rawKey(key);
List<byte[]> rawValues = execute(connection -> connection.hRandField(rawKey, count), true);
@@ -138,7 +138,7 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
*/
@Nullable
@Override
public Map<HK, HV> randomValues(K key, long count) {
public Map<HK, HV> randomEntries(K key, long count) {
Assert.isTrue(count > 0, "Count must not be negative");
byte[] rawKey = rawKey(key);
@@ -324,24 +324,7 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
@Override
public Entry<HK, HV> convert(final Entry<byte[], byte[]> source) {
return new Entry<HK, HV>() {
@Override
public HK getKey() {
return deserializeHashKey(source.getKey());
}
@Override
public HV getValue() {
return deserializeHashValue(source.getValue());
}
@Override
public HV setValue(HV value) {
throw new UnsupportedOperationException("Values cannot be set when scanning through entries.");
}
};
return Converters.entryOf(deserializeHashKey(source.getKey()), deserializeHashValue(source.getValue()));
}
}));

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.core;
import org.springframework.data.redis.connection.convert.Converters;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -149,7 +150,7 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
* @see org.springframework.data.redis.core.ReactiveHashOperations#randomField(H)
*/
@Override
public Mono<HK> randomField(H key) {
public Mono<HK> randomKey(H key) {
Assert.notNull(key, "Key must not be null!");
@@ -162,7 +163,7 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
* @see org.springframework.data.redis.core.ReactiveHashOperations#randomValue(H)
*/
@Override
public Mono<Map.Entry<HK, HV>> randomValue(H key) {
public Mono<Map.Entry<HK, HV>> randomEntry(H key) {
Assert.notNull(key, "Key must not be null!");
@@ -175,7 +176,7 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
* @see org.springframework.data.redis.core.ReactiveHashOperations#randomFields(H, long)
*/
@Override
public Flux<HK> randomFields(H key, long count) {
public Flux<HK> randomKeys(H key, long count) {
Assert.notNull(key, "Key must not be null!");
@@ -188,7 +189,7 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
* @see org.springframework.data.redis.core.ReactiveHashOperations#randomValues(H, long)
*/
@Override
public Flux<Map.Entry<HK, HV>> randomValues(H key, long count) {
public Flux<Map.Entry<HK, HV>> randomEntries(H key, long count) {
Assert.notNull(key, "Key must not be null!");
@@ -353,8 +354,7 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
}
private Map.Entry<HK, HV> deserializeHashEntry(Map.Entry<ByteBuffer, ByteBuffer> source) {
return Collections.singletonMap(readHashKey(source.getKey()), readHashValue(source.getValue())).entrySet()
.iterator().next();
return Converters.entryOf(readHashKey(source.getKey()), readHashValue(source.getValue()));
}
private List<HV> deserializeHashValues(List<ByteBuffer> source) {

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.core;
import org.springframework.data.redis.connection.convert.Converters;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -422,8 +423,7 @@ class DefaultReactiveStreamOperations<K, HK, HV> implements ReactiveStreamOperat
}
private Entry<HK, HV> deserializeRecordFields(Entry<ByteBuffer, ByteBuffer> it) {
return Collections.singletonMap(readHashKey(it.getKey()), deserializeHashValue(it.getValue())).entrySet().iterator()
.next();
return Converters.entryOf(readHashKey(it.getKey()), deserializeHashValue(it.getValue()));
}
private ByteBufferRecord serializeRecord(MapRecord<K, ? extends HK, ? extends HV> record) {
@@ -432,6 +432,6 @@ class DefaultReactiveStreamOperations<K, HK, HV> implements ReactiveStreamOperat
}
private Entry<ByteBuffer, ByteBuffer> serializeRecordFields(Entry<? extends HK, ? extends HV> it) {
return Collections.singletonMap(rawHashKey(it.getKey()), rawValue(it.getValue())).entrySet().iterator().next();
return Converters.entryOf(rawHashKey(it.getKey()), rawValue(it.getValue()));
}
}

View File

@@ -89,7 +89,7 @@ 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}.
* Return a random hash key (aka 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.
@@ -97,10 +97,10 @@ public interface HashOperations<H, HK, HV> {
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
HK randomField(H key);
HK randomKey(H key);
/**
* Return a random field from the hash value stored at {@code key}.
* Return a random entry 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.
@@ -108,12 +108,12 @@ public interface HashOperations<H, HK, HV> {
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
Map.Entry<HK, HV> randomValue(H key);
Map.Entry<HK, HV> randomEntry(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,
* Return random hash keys (aka fields) from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct hash keys, 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 hash key 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}.
@@ -123,10 +123,10 @@ public interface HashOperations<H, HK, HV> {
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
List<HK> randomFields(H key, long count);
List<HK> randomKeys(H key, long count);
/**
* Return a random field from the hash value stored at {@code key}.
* Return a random entries 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.
@@ -135,7 +135,7 @@ public interface HashOperations<H, HK, HV> {
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
@Nullable
Map<HK, HV> randomValues(H key, long count);
Map<HK, HV> randomEntries(H key, long count);
/**
* Get key set (fields) of hash at {@code key}.

View File

@@ -88,29 +88,29 @@ 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}.
* Return a random hash key (aka 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);
Mono<HK> randomKey(H key);
/**
* Return a random field from the hash value stored at {@code key}.
* Return a random entry 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);
Mono<Map.Entry<HK, HV>> randomEntry(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,
* Return random hash keys (aka fields) from the hash value stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct hash keys, 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 hash key 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}.
@@ -119,12 +119,12 @@ public interface ReactiveHashOperations<H, HK, HV> {
* @since 2.6
* @see <a href="https://redis.io/commands/hrandfield">Redis Documentation: HRANDFIELD</a>
*/
Flux<HK> randomFields(H key, long count);
Flux<HK> randomKeys(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,
* Return random entries from the hash stored at {@code key}. If the provided {@code count} argument is
* positive, return a list of distinct entries, 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 entry 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}.
@@ -133,7 +133,7 @@ public interface ReactiveHashOperations<H, HK, HV> {
* @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);
Flux<Map.Entry<HK, HV>> randomEntries(H key, long count);
/**
* Get key set (fields) of hash at {@code key}.

View File

@@ -81,6 +81,24 @@ public class DefaultRedisMap<K, V> implements RedisMap<K, V> {
return hashOps.increment(key, delta);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.support.collections.RedisMap#randomKey()
*/
@Override
public K randomKey() {
return hashOps.randomKey();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.support.collections.RedisMap#randomEntry()
*/
@Override
public Entry<K, V> randomEntry() {
return hashOps.randomEntry();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.support.collections.RedisStore#getOperations()

View File

@@ -19,6 +19,8 @@ import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import org.springframework.lang.Nullable;
/**
* Map view of a Redis hash.
*
@@ -27,10 +29,43 @@ import java.util.concurrent.ConcurrentMap;
*/
public interface RedisMap<K, V> extends RedisStore, ConcurrentMap<K, V> {
/**
* Increment {@code value} of the hash {@code key} by the given {@code delta}.
*
* @param key must not be {@literal null}.
* @param delta
* @return {@literal null} if hash does not exist.
* @since 1.0
*/
Long increment(K key, long delta);
/**
* Increment {@code value} of the hash {@code key} by the given {@code delta}.
*
* @param key must not be {@literal null}.
* @param delta
* @return {@literal null} if hash does not exist.
* @since 1.1
*/
Double increment(K key, double delta);
/**
* Get a random key from the hash.
*
* @return {@literal null} if the hash does not exist.
* @since 2.6
*/
K randomKey();
/**
* Get a random entry from the hash.
*
* @return {@literal null} if the hash does not exist.
* @since 2.6
*/
@Nullable
Map.Entry<K,V> randomEntry();
/**
* @since 1.4
* @return

View File

@@ -304,6 +304,24 @@ public class RedisProperties extends Properties implements RedisMap<Object, Obje
return hashOps.increment((String) key, delta);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.support.collections.RedisMap#randomKey()
*/
@Override
public Object randomKey() {
return delegate.randomKey();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.support.collections.RedisMap#randomEntry()
*/
@Override
public Entry<Object, Object> randomEntry() {
return (Entry) delegate.randomEntry();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.support.collections.RedisStore#getOperations()