diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index d2c2cfc4f..ba4eebf71 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`, `HRANDFIELD`, `ZRANDMEMBER`). +* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`, `COPY`, `GETEX`, `GETDEL`, `ZPOPMIN`, `BZPOPMIN`, `ZPOPMAX`, `BZPOPMAX`, `ZMSCORE`, `ZDIFF`, `ZDIFFSTORE`, `ZINTER`, `ZUNION`, `HRANDFIELD`, `ZRANDMEMBER`, `SMISMEMBER`). [[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 40dffba45..f9c1ebf5f 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -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 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 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 c8923e4b6..db915da29 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 @@ public interface DefaultedRedisConnection extends RedisConnection { 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 7da72e25a..ee9adcfdc 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.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> 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 e9fb5765b..5a03c0704 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 66cf8a131..23b33dd38 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 @@ 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 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 72bcb1e74..7031e39ad 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 @@ class JedisClusterSetCommands implements RedisSetCommands { } } + /* + * (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 6f845145d..5a3394240 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 @@ 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 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 873193aa4..00bceee75 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 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> 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 802288a02..68a3c5dc4 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 @@ 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 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 6d0a79ca9..0ef9a697d 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 @@ 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 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 d504402ed..2033e8c83 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 @@ 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 extends DefaultBoundKeyOperations imple 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 4a7355062..1f1ba2adf 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.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 implements ReactiveSetOperations 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 b2804e426..bf3e37a69 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 @@ 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 extends AbstractOperations 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 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 8be5a8286..c18929d6d 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.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 { */ 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 06f21a8fb..0894c3f4a 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 @@ 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 { @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 68027f187..abc04d67d 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 @@ -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 extends AbstractRedisCollection implements RedisSet { @@ -231,6 +233,23 @@ public class DefaultRedisSet extends AbstractRedisCollection 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 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/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index 77418679a..212d4ee7f 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -1656,6 +1656,15 @@ public abstract class AbstractConnectionIntegrationTests { 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 2302d31ec..9e4d9aeec 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 a6aa682b4..e6a9e255a 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 class JedisClusterConnectionTests implements ClusterConnectionTests { 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 e89a54a9f..e2f6acf1b 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 class LettuceClusterConnectionTests implements ClusterConnectionTests { 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 2e967b1d4..832724d14 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 reactor.test.StepVerifier; 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 @@ public class LettuceReactiveSetCommandsIntegrationIntegrationTests extends Lettu 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 c2c99e779..977df8e8c 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.ObjectFactory; 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 @@ public class DefaultReactiveSetOperationsIntegrationTests { 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 111b577f9..2f250c301 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 java.util.Set; 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 @@ public class DefaultSetOperationsIntegrationTests { 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 68937dade..848101f9c 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 static org.assertj.core.api.Assertions.*; 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.ObjectFactory; 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 @@ public abstract class AbstractRedisSetIntegrationTests extends AbstractRedisC 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");