diff --git a/pom.xml b/pom.xml index 8657b1992a..e6a3f337d5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-redis - 2.6.0-SNAPSHOT + 2.6.0-2037-SNAPSHOT Spring Data Redis diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 920916f201..5052a2be5a 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -7,7 +7,7 @@ This section briefly covers items that are new and noteworthy in the latest rele == New in Spring Data Redis 2.6 * Support for `SubscriptionListener` when using `MessageListener` for subscription confirmation callbacks. `ReactiveRedisMessageListenerContainer` and `ReactiveRedisOperations` provide `receiveLater(…)` and `listenToLater(…)` methods to await until Redis acknowledges the subscription. -* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`, `COPY`, `GETEX`, `GETDEL`, `ZPOPMIN`, `BZPOPMIN`, `ZPOPMAX`, `BZPOPMAX`, `ZMSCORE`, `ZDIFF`, `ZDIFFSTORE`, `ZINTER`, `ZUNION`). +* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`, `COPY`, `GETEX`, `GETDEL`, `SMISMEMBER`, `ZPOPMIN`, `BZPOPMIN`, `ZPOPMAX`, `BZPOPMAX`, `ZMSCORE`, `ZDIFF`, `ZDIFFSTORE`, `ZINTER`, `ZUNION`). [[new-in-2.5.0]] == New in Spring Data Redis 2.5 diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index 34222cb937..b3b16c43ca 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -1164,6 +1164,15 @@ public Boolean sIsMember(byte[] key, byte[] value) { return convertAndReturn(delegate.sIsMember(key, value), Converters.identityConverter()); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisSetCommands#sIsMember(byte[], byte[]...) + */ + @Override + public List sMIsMember(byte[] key, byte[]... values) { + return convertAndReturn(delegate.sMIsMember(key, values), Converters.identityConverter()); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisSetCommands#sMembers(byte[]) @@ -2766,6 +2775,15 @@ public Boolean sIsMember(String key, String value) { 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 sMIsMember(String key, String... values) { + return sMIsMember(serialize(key), serializeMulti(values)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#sMembers(java.lang.String) diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java index d7b2f346aa..ea16e5df17 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -835,6 +835,13 @@ default Boolean sIsMember(byte[] key, byte[] value) { return setCommands().sIsMember(key, value); } + /** @deprecated in favor of {@link RedisConnection#setCommands()}}. */ + @Override + @Deprecated + default List sMIsMember(byte[] key, byte[]... value) { + return setCommands().sMIsMember(key, value); + } + /** @deprecated in favor of {@link RedisConnection#setCommands()}}. */ @Override @Deprecated diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java index 7da72e25aa..ee9adcfdc8 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java @@ -32,6 +32,7 @@ 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 @@ default Mono sIsMember(ByteBuffer key, ByteBuffer value) { } /** - * 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 @@ default Mono sIsMember(ByteBuffer key, ByteBuffer value) { */ Flux> sIsMember(Publisher commands); + /** + * {@code SMISMEMBER} command parameters. + * + * @author Mark Paluch + * @since 2.6 + * @see Redis Documentation: SMISMEMBER + */ + class SMIsMemberCommand extends KeyCommand { + + private final List values; + + private SMIsMemberCommand(@Nullable ByteBuffer key, List 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 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 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 Redis Documentation: SMISMEMBER + */ + default Mono> sMIsMember(ByteBuffer key, List 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 Redis Documentation: SMISMEMBER + */ + Flux> sMIsMember(Publisher commands); + /** * {@code SINTER} command parameters. * diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java index e9fb5765bb..5a03c0704a 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java @@ -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 Redis Documentation: SMISMEMBER + */ + @Nullable + List sMIsMember(byte[] key, byte[]... values); + /** * Diff all sets for given {@code keys}. * diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java index 469aeed494..50ba6e7903 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -1043,6 +1043,19 @@ default Long lPos(String key, String element) { */ 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 Redis Documentation: SMISMEMBER + * @see RedisSetCommands#sMIsMember(byte[], byte[]...) + */ + @Nullable + List sMIsMember(String key, String... values); + /** * Returns the members intersecting all given sets at {@code keys}. * diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterSetCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterSetCommands.java index 72bcb1e746..7031e39ad9 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterSetCommands.java @@ -177,6 +177,24 @@ public Boolean sIsMember(byte[] key, byte[] value) { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisSetCommands#sMIsMember(byte[], byte[]...) + */ + @Override + public List 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[][]) diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisSetCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisSetCommands.java index 6f845145d0..5a33942403 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisSetCommands.java @@ -136,6 +136,20 @@ public Boolean sIsMember(byte[] key, byte[] value) { 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 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[]) diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveSetCommands.java index 873193aa42..00bceee75a 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveSetCommands.java @@ -22,11 +22,13 @@ 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 @@ public Flux> sIsMember(Publisher> sMIsMember(Publisher 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) diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceSetCommands.java index 802288a022..68a3c5dc4f 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceSetCommands.java @@ -137,6 +137,20 @@ public Boolean sIsMember(byte[] key, byte[] value) { return connection.invoke().just(RedisSetAsyncCommands::sismember, key, value); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisSetCommands#sMIsMember(byte[], byte[]...) + */ + @Override + public List 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[]) diff --git a/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java b/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java index 6d0a79ca9f..0ef9a697d6 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java @@ -17,6 +17,7 @@ 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 extends BoundKeyOperations { @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 Redis Documentation: SMISMEMBER + */ + @Nullable + Map isMember(Object... objects); + /** * Returns the members intersecting all given sets at the bound key and {@code key}. * diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundSetOperations.java index d504402edc..2033e8c839 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundSetOperations.java @@ -18,6 +18,7 @@ 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 @@ public Boolean isMember(Object o) { return ops.isMember(getKey(), o); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundSetOperations#isMember(java.lang.Object...) + */ + @Override + public Map isMember(Object... objects) { + return ops.isMember(getKey(), objects); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundSetOperations#members() diff --git a/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java index 4a73550628..1f1ba2adf3 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java @@ -22,7 +22,9 @@ 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 @@ public Mono isMember(K key, Object o) { 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> 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 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) diff --git a/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java index b2804e4264..bf3e37a694 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java @@ -18,7 +18,9 @@ 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 @@ public Boolean isMember(K key, Object o) { 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 isMember(K key, Object... objects) { + + byte[] rawKey = rawKey(key); + byte[][] rawValues = rawValues(objects); + + return execute(connection -> { + + List result = connection.sMIsMember(rawKey, rawValues); + + if (result == null || result.size() != objects.length) { + return null; + } + + Map 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) diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java index 8be5a82867..c18929d6d9 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java @@ -19,6 +19,7 @@ import reactor.core.publisher.Mono; import java.util.Collection; +import java.util.Map; /** * Redis set specific operations. @@ -100,6 +101,17 @@ public interface ReactiveSetOperations { */ Mono 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 Redis Documentation: SMISMEMBER + */ + Mono> isMember(K key, Object... objects); + /** * Returns the members intersecting all given sets at {@code key} and {@code otherKey}. * diff --git a/src/main/java/org/springframework/data/redis/core/SetOperations.java b/src/main/java/org/springframework/data/redis/core/SetOperations.java index 06f21a8fbc..0894c3f4a2 100644 --- a/src/main/java/org/springframework/data/redis/core/SetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/SetOperations.java @@ -17,6 +17,7 @@ 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 { @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 Redis Documentation: SMISMEMBER + */ + @Nullable + Map isMember(K key, Object... objects); + /** * Returns the members intersecting all given sets at {@code key} and {@code otherKey}. * diff --git a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisSet.java b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisSet.java index 993c657ac2..e04ee557c9 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisSet.java @@ -16,10 +16,9 @@ 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; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.core.BoundSetOperations; @@ -33,6 +32,7 @@ * * @author Costin Leau * @author Christoph Strobl + * @author Mark Paluch */ public class DefaultRedisSet extends AbstractRedisCollection implements RedisSet { @@ -205,10 +205,7 @@ public boolean add(E e) { */ @Override public void clear() { - // intersect the set with a non existing one - // TODO: find a safer way to clean the set - String randomKey = UUID.randomUUID().toString(); - boundSetOps.intersectAndStore(Collections.singleton(randomKey), getKey()); + boundSetOps.getOperations().delete(getKey()); } /* @@ -222,6 +219,23 @@ public boolean contains(Object o) { return result; } + /* + * (non-Javadoc) + * @see java.util.AbstractCollection#containsAll(java.util.Collection) + */ + @Override + public boolean containsAll(Collection c) { + + if (c.isEmpty()) { + return true; + } + + Map member = boundSetOps.isMember(c.toArray()); + checkResult(member); + + return member.values().stream().reduce(true, (left, right) -> left && right); + } + /* * (non-Javadoc) * @see java.util.AbstractCollection#iterator() diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisSet.java b/src/main/java/org/springframework/data/redis/support/collections/RedisSet.java index 48e043e2f2..254507a200 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisSet.java @@ -19,37 +19,135 @@ import java.util.Iterator; import java.util.Set; +import org.springframework.data.redis.core.RedisOperations; + /** * Redis extension for the {@link Set} contract. Supports {@link Set} specific operations backed by Redis operations. * * @author Costin Leau * @author Christoph Strobl + * @author Mark Paluch */ public interface RedisSet extends RedisCollection, Set { - Set intersect(RedisSet set); + /** + * Constructs a new {@link RedisSet} instance. + * + * @param key Redis key of this set. + * @param operations {@link RedisOperations} for the value type of this set. + * @since 2.6 + */ + static RedisSet create(String key, RedisOperations operations) { + return new DefaultRedisSet<>(key, operations); + } - Set intersect(Collection> sets); + /** + * Diff this set and another {@link RedisSet}. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the values that differ. + */ + Set diff(RedisSet set); - Set union(RedisSet set); + /** + * Diff this set and other {@link RedisSet}s. + * + * @param sets must not be {@literal null}. + * @return a {@link Set} containing the values that differ. + */ + Set diff(Collection> sets); - Set union(Collection> sets); + /** + * Create a new {@link RedisSet} by diffing this set and {@link RedisSet} and store result in destination + * {@code destKey}. + * + * @param set must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisSet} pointing at {@code destKey}. + */ + RedisSet diffAndStore(RedisSet set, String destKey); - Set diff(RedisSet set); + /** + * Create a new {@link RedisSet} by diffing this set and the collection {@link RedisSet} and store result in + * destination {@code destKey}. + * + * @param sets must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisSet} pointing at {@code destKey}. + */ + RedisSet diffAndStore(Collection> sets, String destKey); - Set diff(Collection> sets); + /** + * Intersect this set and another {@link RedisSet}. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the intersecting values. + */ + Set intersect(RedisSet set); + /** + * Intersect this set and other {@link RedisSet}s. + * + * @param sets must not be {@literal null}. + * @return a {@link Set} containing the intersecting values. + */ + Set intersect(Collection> sets); + + /** + * Create a new {@link RedisSet} by intersecting this set and {@link RedisSet} and store result in destination + * {@code destKey}. + * + * @param set must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisSet} pointing at {@code destKey} + */ RedisSet intersectAndStore(RedisSet set, String destKey); + /** + * Create a new {@link RedisSet} by intersecting this set and the collection {@link RedisSet} and store result in + * destination {@code destKey}. + * + * @param sets must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisSet} pointing at {@code destKey} + */ RedisSet intersectAndStore(Collection> sets, String destKey); - RedisSet unionAndStore(RedisSet set, String destKey); + /** + * Union this set and another {@link RedisSet}. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the combined values. + */ + Set union(RedisSet set); - RedisSet unionAndStore(Collection> sets, String destKey); + /** + * Union this set and other {@link RedisSet}s. + * + * @param sets must not be {@literal null}. + * @return a {@link Set} containing the combined values. + */ + Set union(Collection> sets); - RedisSet diffAndStore(RedisSet set, String destKey); + /** + * Create a new {@link RedisSet} by union this set and {@link RedisSet} and store result in destination + * {@code destKey}. + * + * @param set must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisSet} pointing at {@code destKey} + */ + RedisSet unionAndStore(RedisSet set, String destKey); - RedisSet diffAndStore(Collection> sets, String destKey); + /** + * Create a new {@link RedisSet} by union this set and the collection {@link RedisSet} and store result in destination + * {@code destKey}. + * + * @param sets must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisSet} pointing at {@code destKey} + */ + RedisSet unionAndStore(Collection> sets, String destKey); /** * @since 1.4 diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index 0aeb887d0a..7fd897a500 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -1610,6 +1610,15 @@ void testSIsMember() { verifyResults(Arrays.asList(new Object[] { 1L, 1L, true, false })); } + @Test // GH-2037 + @EnabledOnCommand("SMISMEMBER") + void testSMIsMember() { + actual.add(connection.sAdd("myset", "foo")); + actual.add(connection.sAdd("myset", "bar")); + actual.add(connection.sMIsMember("myset", "foo", "bar", "baz")); + verifyResults(Arrays.asList(new Object[] { 1L, 1L, Arrays.asList(true, true, false) })); + } + @Test void testSMove() { actual.add(connection.sAdd("myset", "foo")); diff --git a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java index f8c1d7b1f7..9565a8a207 100644 --- a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java @@ -470,6 +470,9 @@ public interface ClusterConnectionTests { // DATAREDIS-315 void sIsMemberShouldReturnTrueIfValueIsMemberOfSet(); + // GH-2037 + void sMIsMemberShouldReturnCorrectValues(); + // DATAREDIS-315 void sMembersShouldReturnValuesContainedInSetCorrectly(); diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java index 158e1c6997..c4f2f87047 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java @@ -1589,6 +1589,16 @@ public void sIsMemberShouldReturnTrueIfValueIsMemberOfSet() { assertThat(clusterConnection.sIsMember(KEY_1_BYTES, VALUE_1_BYTES)).isTrue(); } + @Test // GH-2037 + @EnabledOnCommand("SMISMEMBER") + public void sMIsMemberShouldReturnCorrectValues() { + + nativeConnection.sadd(KEY_1, VALUE_1, VALUE_2); + + assertThat(clusterConnection.sMIsMember(KEY_1_BYTES, VALUE_1_BYTES, VALUE_2_BYTES, VALUE_3_BYTES)) + .containsExactly(true, true, false); + } + @Test // DATAREDIS-315 public void sMembersShouldReturnValuesContainedInSetCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java index acb2aa4cf9..528194fb2d 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java @@ -1624,6 +1624,16 @@ public void sIsMemberShouldReturnTrueIfValueIsMemberOfSet() { assertThat(clusterConnection.sIsMember(KEY_1_BYTES, VALUE_1_BYTES)).isTrue(); } + @Test // GH-2037 + @EnabledOnCommand("SMISMEMBER") + public void sMIsMemberShouldReturnCorrectValues() { + + nativeConnection.sadd(KEY_1, VALUE_1, VALUE_2); + + assertThat(clusterConnection.sMIsMember(KEY_1_BYTES, VALUE_1_BYTES, VALUE_2_BYTES, VALUE_3_BYTES)) + .containsExactly(true, true, false); + } + @Test // DATAREDIS-315 public void sMembersShouldReturnValuesContainedInSetCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveSetCommandsIntegrationIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveSetCommandsIntegrationIntegrationTests.java index 2e967b1d4d..832724d147 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveSetCommandsIntegrationIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveSetCommandsIntegrationIntegrationTests.java @@ -22,6 +22,7 @@ import java.util.Arrays; import org.springframework.data.redis.core.ScanOptions; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; /** @@ -143,6 +144,18 @@ void sIsMemberShouldReturnFalseWhenValueNotContainedInKey() { assertThat(connection.setCommands().sIsMember(KEY_1_BBUFFER, VALUE_3_BBUFFER).block()).isFalse(); } + @ParameterizedRedisTest // GH-2037 + @EnabledOnCommand("SMISMEMBER") + void sMIsMemberShouldReturnCorrectly() { + + nativeCommands.sadd(KEY_1, VALUE_1, VALUE_2); + + connection.setCommands().sMIsMember(KEY_1_BBUFFER, Arrays.asList(VALUE_1_BBUFFER, VALUE_3_BBUFFER)) // + .as(StepVerifier::create) // + .expectNext(Arrays.asList(true, false)) // + .verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-525, DATAREDIS-647 void sInterShouldIntersectSetsCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java index c2c99e779e..977df8e8c3 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java @@ -30,6 +30,7 @@ import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.ReactiveOperationsTestParams.Fixture; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.parametrized.MethodSource; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; @@ -154,6 +155,23 @@ void isMember() { setOperations.isMember(key, value1).as(StepVerifier::create).expectNext(true).verifyComplete(); } + @ParameterizedRedisTest // GH-2037 + @EnabledOnCommand("SMISMEMBER") + void isMembers() { + + assumeThat(valueFactory instanceof ByteBufferObjectFactory).isFalse(); + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + setOperations.add(key, value1).as(StepVerifier::create).expectNext(1L).verifyComplete(); + setOperations.isMember(key, value1, value2).as(StepVerifier::create).consumeNextWith(actual -> { + + assertThat(actual).containsEntry(value1, true).containsEntry(value2, false); + }).verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-602, DATAREDIS-873 void intersect() { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsIntegrationTests.java index 111b577f9a..2f250c3018 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsIntegrationTests.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.BeforeEach; import org.springframework.data.redis.ObjectFactory; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.parametrized.MethodSource; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; @@ -322,4 +323,23 @@ void intersectAndStoreShouldReturnNumberOfElementsInDestination() { assertThat(setOps.intersectAndStore(sourceKey1, sourceKey2, destinationKey)).isEqualTo(2L); assertThat(setOps.intersectAndStore(Arrays.asList(sourceKey1, sourceKey2), destinationKey)).isEqualTo(2L); } + + @ParameterizedRedisTest // GH-2037 + @EnabledOnCommand("SMISMEMBER") + void isMember() { + + K key = keyFactory.instance(); + + V v1 = valueFactory.instance(); + V v2 = valueFactory.instance(); + V v3 = valueFactory.instance(); + V v4 = valueFactory.instance(); + + setOps.add(key, v1, v2, v3); + + assertThat(setOps.isMember(key, v1)).isTrue(); + assertThat(setOps.isMember(key, v4)).isFalse(); + assertThat(setOps.isMember(key, v1, v2, v3, v4)).containsEntry(v1, true).containsEntry(v2, true) + .containsEntry(v3, true).containsEntry(v4, false); + } } diff --git a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisSetIntegrationTests.java b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisSetIntegrationTests.java index 11ede63c02..fedde324c7 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisSetIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisSetIntegrationTests.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -30,6 +31,7 @@ import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; import org.springframework.util.ObjectUtils; @@ -67,6 +69,21 @@ private RedisSet createSetFor(String key) { return new DefaultRedisSet<>((BoundSetOperations) set.getOperations().boundSetOps(key)); } + @ParameterizedRedisTest // GH-2037 + @EnabledOnCommand("SMISMEMBER") + void testContainsAll() { + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + + set.add(t1); + set.add(t2); + + assertThat(set.containsAll(Arrays.asList(t1, t2, t3))).isFalse(); + assertThat(set.containsAll(Arrays.asList(t1, t2))).isTrue(); + assertThat(set.containsAll(Collections.emptyList())).isTrue(); + } + @ParameterizedRedisTest void testDiff() { RedisSet diffSet1 = createSetFor("test:set:diff1");