Add support for SMISMEMBER.

Allows querying with a single command whether multiple elements are members of a set. Also, refine DefaultRedisSet.containsAll(…) implementation.

Closes #2037
Original Pull Request: #2105
This commit is contained in:
Mark Paluch
2021-06-29 15:23:30 +02:00
committed by Christoph Strobl
parent 280d9541da
commit 90095d1c89
25 changed files with 425 additions and 2 deletions

View File

@@ -1169,6 +1169,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return convertAndReturn(delegate.sIsMember(key, value), Converters.identityConverter());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSetCommands#sIsMember(byte[], byte[]...)
*/
@Override
public List<Boolean> sMIsMember(byte[] key, byte[]... values) {
return convertAndReturn(delegate.sMIsMember(key, values), Converters.identityConverter());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSetCommands#sMembers(byte[])
@@ -2853,6 +2862,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return sIsMember(serialize(key), serialize(value));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#sMIsMember(java.lang.String, java.lang.String...)
*/
@Override
public List<Boolean> sMIsMember(String key, String... values) {
return sMIsMember(serialize(key), serializeMulti(values));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#sMembers(java.lang.String)

View File

@@ -835,6 +835,13 @@ public interface DefaultedRedisConnection extends RedisConnection {
return setCommands().sIsMember(key, value);
}
/** @deprecated in favor of {@link RedisConnection#setCommands()}}. */
@Override
@Deprecated
default List<Boolean> sMIsMember(byte[] key, byte[]... value) {
return setCommands().sMIsMember(key, value);
}
/** @deprecated in favor of {@link RedisConnection#setCommands()}}. */
@Override
@Deprecated

View File

@@ -32,6 +32,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.Command
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
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.core.ScanOptions;
import org.springframework.lang.Nullable;
@@ -546,7 +547,7 @@ public interface ReactiveSetCommands {
}
/**
* Check if set at {@link SIsMemberCommand#getKey()} contains {@link SIsMemberCommand#getKey()}.
* Check if set at {@link SIsMemberCommand#getKey()} contains {@link SIsMemberCommand#getValue()}.
*
* @param commands must not be {@literal null}.
* @return
@@ -554,6 +555,86 @@ public interface ReactiveSetCommands {
*/
Flux<BooleanResponse<SIsMemberCommand>> sIsMember(Publisher<SIsMemberCommand> commands);
/**
* {@code SMISMEMBER} command parameters.
*
* @author Mark Paluch
* @since 2.6
* @see <a href="https://redis.io/commands/smismember">Redis Documentation: SMISMEMBER</a>
*/
class SMIsMemberCommand extends KeyCommand {
private final List<ByteBuffer> values;
private SMIsMemberCommand(@Nullable ByteBuffer key, List<ByteBuffer> values) {
super(key);
this.values = values;
}
/**
* Creates a new {@link SMIsMemberCommand} given one or more {@literal values}.
*
* @param value must not be {@literal null}.
* @return a new {@link SMIsMemberCommand} for a {@literal value}.
*/
public static SMIsMemberCommand values(List<ByteBuffer> values) {
Assert.notNull(values, "Values must not be null!");
Assert.notEmpty(values, "Values must not be empty!");
return new SMIsMemberCommand(null, values);
}
/**
* Applies the {@literal set} key. Constructs a new command instance with all previously configured properties.
*
* @param set must not be {@literal null}.
* @return a new {@link SMIsMemberCommand} with {@literal set} applied.
*/
public SMIsMemberCommand of(ByteBuffer set) {
Assert.notNull(set, "Set key must not be null!");
return new SMIsMemberCommand(set, values);
}
/**
* @return
*/
public List<ByteBuffer> getValues() {
return values;
}
}
/**
* Check if set at {@code key} contains one or more {@code values}.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/smismember">Redis Documentation: SMISMEMBER</a>
*/
default Mono<List<Boolean>> sMIsMember(ByteBuffer key, List<ByteBuffer> values) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(values, "Value must not be null!");
return sMIsMember(Mono.just(SMIsMemberCommand.values(values).of(key))).next().map(MultiValueResponse::getOutput);
}
/**
* Check if set at {@link SMIsMemberCommand#getKey()} contains {@link SMIsMemberCommand#getValues()}.
*
* @param commands must not be {@literal null}.
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/smismember">Redis Documentation: SMISMEMBER</a>
*/
Flux<MultiValueResponse<SMIsMemberCommand, Boolean>> sMIsMember(Publisher<SMIsMemberCommand> commands);
/**
* {@code SINTER} command parameters.
*

View File

@@ -108,6 +108,18 @@ public interface RedisSetCommands {
@Nullable
Boolean sIsMember(byte[] key, byte[] value);
/**
* Check if set at {@code key} contains one or more {@code values}.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/smismember">Redis Documentation: SMISMEMBER</a>
*/
@Nullable
List<Boolean> sMIsMember(byte[] key, byte[]... values);
/**
* Diff all sets for given {@code keys}.
*

View File

@@ -1043,6 +1043,19 @@ public interface StringRedisConnection extends RedisConnection {
*/
Boolean sIsMember(String key, String value);
/**
* Check if set at {@code key} contains one or more {@code values}.
*
* @param key must not be {@literal null}.
* @param values must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/smismember">Redis Documentation: SMISMEMBER</a>
* @see RedisSetCommands#sMIsMember(byte[], byte[]...)
*/
@Nullable
List<Boolean> sMIsMember(String key, String... values);
/**
* Returns the members intersecting all given sets at {@code keys}.
*

View File

@@ -177,6 +177,24 @@ class JedisClusterSetCommands implements RedisSetCommands {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSetCommands#sMIsMember(byte[], byte[]...)
*/
@Override
public List<Boolean> sMIsMember(byte[] key, byte[]... values) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(values, "Value must not be null!");
Assert.noNullElements(values, "Values must not contain null elements!");
try {
return connection.getCluster().smismember(key, values);
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSetCommands#sInter(byte[][])

View File

@@ -136,6 +136,20 @@ class JedisSetCommands implements RedisSetCommands {
return connection.invoke().just(BinaryJedis::sismember, MultiKeyPipelineBase::sismember, key, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSetCommands#sMIsMember(byte[], byte[]...)
*/
@Override
public List<Boolean> sMIsMember(byte[] key, byte[]... values) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(values, "Values must not be null!");
Assert.noNullElements(values, "Values must not contain null elements!");
return connection.invoke().just(BinaryJedis::smismember, MultiKeyPipelineBase::smismember, key, values);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSetCommands#sMembers(byte[])

View File

@@ -22,11 +22,13 @@ import reactor.core.publisher.Mono;
import java.nio.ByteBuffer;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
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.ReactiveSetCommands;
import org.springframework.util.Assert;
@@ -163,6 +165,23 @@ class LettuceReactiveSetCommands implements ReactiveSetCommands {
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sMIsMember(org.reactivestreams.Publisher)
*/
@Override
public Flux<MultiValueResponse<SMIsMemberCommand, Boolean>> sMIsMember(Publisher<SMIsMemberCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getValues(), "Values must not be null!");
return cmd.smismember(command.getKey(), command.getValues().toArray(new ByteBuffer[0])).collectList()
.map(value -> new MultiValueResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sInter(org.reactivestreams.Publisher)

View File

@@ -137,6 +137,20 @@ class LettuceSetCommands implements RedisSetCommands {
return connection.invoke().just(RedisSetAsyncCommands::sismember, key, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSetCommands#sMIsMember(byte[], byte[]...)
*/
@Override
public List<Boolean> sMIsMember(byte[] key, byte[]... values) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(values, "Values must not be null!");
Assert.noNullElements(values, "Values must not contain null elements!");
return connection.invoke().just(RedisSetAsyncCommands::smismember, key, values);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSetCommands#sMembers(byte[])

View File

@@ -17,6 +17,7 @@ package org.springframework.data.redis.core;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.lang.Nullable;
@@ -88,6 +89,18 @@ public interface BoundSetOperations<K, V> extends BoundKeyOperations<K> {
@Nullable
Boolean isMember(Object o);
/**
* Check if set at at the bound key contains one or more {@code values}.
*
* @param key must not be {@literal null}.
* @param objects
* @return {@literal null} when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/smismember">Redis Documentation: SMISMEMBER</a>
*/
@Nullable
Map<Object, Boolean> isMember(Object... objects);
/**
* Returns the members intersecting all given sets at the bound key and {@code key}.
*

View File

@@ -18,6 +18,7 @@ package org.springframework.data.redis.core;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.data.redis.connection.DataType;
@@ -143,6 +144,15 @@ class DefaultBoundSetOperations<K, V> extends DefaultBoundKeyOperations<K> imple
return ops.isMember(getKey(), o);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundSetOperations#isMember(java.lang.Object...)
*/
@Override
public Map<Object, Boolean> isMember(Object... objects) {
return ops.isMember(getKey(), objects);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundSetOperations#members()

View File

@@ -22,7 +22,9 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.reactivestreams.Publisher;
@@ -151,6 +153,34 @@ class DefaultReactiveSetOperations<K, V> implements ReactiveSetOperations<K, V>
return createMono(connection -> connection.sIsMember(rawKey(key), rawValue((V) o)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveSetOperations#isMember(java.lang.Object, java.lang.Object...)
*/
@Override
public Mono<Map<Object, Boolean>> isMember(K key, Object... objects) {
Assert.notNull(key, "Key must not be null!");
return createMono(connection -> {
return Flux.fromArray((V[]) objects) //
.map(this::rawValue) //
.collectList() //
.flatMap(rawValues -> connection.sMIsMember(rawKey(key), rawValues)) //
.map(result -> {
Map<Object, Boolean> isMember = new LinkedHashMap<>(result.size());
for (int i = 0; i < objects.length; i++) {
isMember.put(objects[i], result.get(i));
}
return isMember;
});
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveSetOperations#intersect(java.lang.Object, java.lang.Object)

View File

@@ -18,7 +18,9 @@ package org.springframework.data.redis.core;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.data.redis.connection.RedisConnection;
@@ -203,6 +205,34 @@ class DefaultSetOperations<K, V> extends AbstractOperations<K, V> implements Set
return execute(connection -> connection.sIsMember(rawKey, rawValue), true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.SetOperations#isMember(java.lang.Object, java.lang.Object...)
*/
@Override
public Map<Object, Boolean> isMember(K key, Object... objects) {
byte[] rawKey = rawKey(key);
byte[][] rawValues = rawValues(objects);
return execute(connection -> {
List<Boolean> result = connection.sMIsMember(rawKey, rawValues);
if (result == null || result.size() != objects.length) {
return null;
}
Map<Object, Boolean> isMember = new LinkedHashMap<>(result.size());
for (int i = 0; i < objects.length; i++) {
isMember.put(objects[i], result.get(i));
}
return isMember;
}, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.SetOperations#members(java.lang.Object)

View File

@@ -19,6 +19,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.Map;
/**
* Redis set specific operations.
@@ -100,6 +101,17 @@ public interface ReactiveSetOperations<K, V> {
*/
Mono<Boolean> isMember(K key, Object o);
/**
* Check if set at {@code key} contains one or more {@code values}.
*
* @param key must not be {@literal null}.
* @param values
* @return
* @since 2.6
* @see <a href="https://redis.io/commands/smismember">Redis Documentation: SMISMEMBER</a>
*/
Mono<Map<Object, Boolean>> isMember(K key, Object... objects);
/**
* Returns the members intersecting all given sets at {@code key} and {@code otherKey}.
*

View File

@@ -17,6 +17,7 @@ package org.springframework.data.redis.core;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.lang.Nullable;
@@ -108,6 +109,18 @@ public interface SetOperations<K, V> {
@Nullable
Boolean isMember(K key, Object o);
/**
* Check if set at {@code key} contains one or more {@code values}.
*
* @param key must not be {@literal null}.
* @param objects
* @return {@literal null} when used in pipeline / transaction.
* @since 2.6
* @see <a href="https://redis.io/commands/smismember">Redis Documentation: SMISMEMBER</a>
*/
@Nullable
Map<Object, Boolean> isMember(K key, Object... objects);
/**
* Returns the members intersecting all given sets at {@code key} and {@code otherKey}.
*

View File

@@ -18,6 +18,7 @@ package org.springframework.data.redis.support.collections;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -33,6 +34,7 @@ import org.springframework.data.redis.core.ScanOptions;
*
* @author Costin Leau
* @author Christoph Strobl
* @author Mark Paluch
*/
public class DefaultRedisSet<E> extends AbstractRedisCollection<E> implements RedisSet<E> {
@@ -231,6 +233,23 @@ public class DefaultRedisSet<E> extends AbstractRedisCollection<E> implements Re
return result;
}
/*
* (non-Javadoc)
* @see java.util.AbstractCollection#containsAll(java.util.Collection)
*/
@Override
public boolean containsAll(Collection<?> c) {
if (c.isEmpty()) {
return true;
}
Map<Object, Boolean> member = boundSetOps.isMember(c.toArray());
checkResult(member);
return member.values().stream().reduce(true, (left, right) -> left && right);
}
/*
* (non-Javadoc)
* @see java.util.AbstractCollection#iterator()