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 754c2b8b29..ff1f40fb24 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -82,6 +82,7 @@ * @author Dennis Neufeld * @author Shyngys Sapraliyev * @author Jeonggyu Choi + * @author Mingi Lee */ @NullUnmarked @SuppressWarnings({ "ConstantConditions", "deprecation" }) @@ -832,6 +833,11 @@ public Long sInterStore(byte[] destKey, byte[]... keys) { return convertAndReturn(delegate.sInterStore(destKey, keys), Converters.identityConverter()); } + @Override + public Long sInterCard(byte[]... keys) { + return convertAndReturn(delegate.sInterCard(keys), Converters.identityConverter()); + } + @Override public Boolean sIsMember(byte[] key, byte[] value) { return convertAndReturn(delegate.sIsMember(key, value), Converters.identityConverter()); @@ -1824,6 +1830,11 @@ public Long sInterStore(String destKey, String... keys) { return sInterStore(serialize(destKey), serializeMulti(keys)); } + @Override + public Long sInterCard(String... keys) { + return sInterCard(serializeMulti(keys)); + } + @Override public Boolean sIsMember(String key, String value) { return sIsMember(serialize(key), serialize(value)); 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 b5976bc082..79ee58eb22 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -67,6 +67,7 @@ * @author Dennis Neufeld * @author Shyngys Sapraliyev * @author Tihomir Mateev + * @author Mingi Lee * @since 2.0 */ @Deprecated @@ -890,6 +891,13 @@ default Long sInterStore(byte[] destKey, byte[]... keys) { return setCommands().sInterStore(destKey, keys); } + /** @deprecated in favor of {@link RedisConnection#setCommands()}}. */ + @Override + @Deprecated + default Long sInterCard(byte[]... keys) { + return setCommands().sInterCard(keys); + } + /** @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 e5e0466cbb..5c3c6031cc 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveSetCommands.java @@ -43,6 +43,7 @@ * * @author Christoph Strobl * @author Mark Paluch + * @author Mingi Lee * @since 2.0 */ public interface ReactiveSetCommands { @@ -775,6 +776,73 @@ default Mono sInterStore(ByteBuffer destinationKey, Collection */ Flux> sInterStore(Publisher commands); + /** + * {@code SINTERCARD} command parameters. + * + * @author Mingi Lee + * @since 4.0 + * @see Redis Documentation: SINTERCARD + */ + class SInterCardCommand implements Command { + + private final List keys; + + private SInterCardCommand(List keys) { + this.keys = keys; + } + + /** + * Creates a new {@link SInterCardCommand} given a {@link Collection} of keys. + * + * @param keys must not be {@literal null}. + * @return a new {@link SInterCardCommand} for a {@link Collection} of keys. + */ + public static SInterCardCommand keys(Collection keys) { + + Assert.notNull(keys, "Keys must not be null"); + + return new SInterCardCommand(new ArrayList<>(keys)); + } + + @Override + public @Nullable ByteBuffer getKey() { + return null; + } + + /** + * @return never {@literal null}. + */ + public List getKeys() { + return keys; + } + } + + /** + * Returns the cardinality of the set which would result from the intersection of all given sets at {@literal keys}. + * + * @param keys must not be {@literal null}. + * @return + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + default Mono sInterCard(Collection keys) { + + Assert.notNull(keys, "Keys must not be null"); + + return sInterCard(Mono.just(SInterCardCommand.keys(keys))).next().map(NumericResponse::getOutput); + } + + /** + * Returns the cardinality of the set which would result from the intersection of all given sets at + * {@link SInterCardCommand#getKeys()}. + * + * @param commands must not be {@literal null}. + * @return + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Flux> sInterCard(Publisher commands); + /** * {@code SUNION} 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 ea39901919..dd1bb725ab 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java @@ -29,6 +29,7 @@ * @author Costin Leau * @author Christoph Strobl * @author Mark Paluch + * @author Mingi Lee * @see RedisCommands */ @NullUnmarked @@ -153,6 +154,16 @@ public interface RedisSetCommands { */ Long sInterStore(byte @NonNull [] destKey, byte @NonNull [] @NonNull... keys); + /** + * Returns the cardinality of the set which would result from the intersection of all the given sets. + * + * @param keys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Long sInterCard(byte @NonNull [] @NonNull... keys); + /** * Union all sets at 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 9e0392e46d..f49dc1a61f 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -74,6 +74,7 @@ * @author ihaohong * @author Shyngys Sapraliyev * @author Jeonggyu Choi + * @author Mingi Lee * @see RedisCallback * @see RedisSerializer * @see StringRedisTemplate @@ -1169,6 +1170,17 @@ String bLMove(@NonNull String sourceKey, @NonNull String destinationKey, @NonNul */ Long sInterStore(@NonNull String destKey, @NonNull String @NonNull... keys); + /** + * Returns the cardinality of the set which would result from the intersection of all the given sets. + * + * @param keys must not be {@literal null}. + * @return + * @see Redis Documentation: SINTERCARD + * @see RedisSetCommands#sInterCard(byte[]...) + * @since 4.0 + */ + Long sInterCard(@NonNull String @NonNull... keys); + /** * Union all sets at given {@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 6a9fc6ef31..de3c66e270 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 @@ -40,6 +40,7 @@ /** * @author Christoph Strobl * @author Mark Paluch + * @author Mingi Lee * @since 2.0 */ class JedisClusterSetCommands implements RedisSetCommands { @@ -230,6 +231,25 @@ public Long sInterStore(byte[] destKey, byte[]... keys) { return sAdd(destKey, result.toArray(new byte[result.size()][])); } + @Override + public Long sInterCard(byte[]... keys) { + + Assert.notNull(keys, "Keys must not be null"); + Assert.noNullElements(keys, "Keys must not contain null elements"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(keys)) { + try { + return connection.getCluster().sintercard(keys); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + // For multi-slot clusters, calculate intersection cardinality by performing intersection + Set result = sInter(keys); + return (long) result.size(); + } + @Override public Set sUnion(byte[]... keys) { 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 ed5aca3b80..61b80f5f8c 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 @@ -38,6 +38,7 @@ /** * @author Christoph Strobl * @author Mark Paluch + * @author Mingi Lee * @since 2.0 */ @NullUnmarked @@ -105,6 +106,15 @@ public Long sInterStore(byte @NonNull [] destKey, byte @NonNull [] @NonNull... k return connection.invoke().just(Jedis::sinterstore, PipelineBinaryCommands::sinterstore, destKey, keys); } + @Override + public Long sInterCard(byte @NonNull [] @NonNull... keys) { + + Assert.notNull(keys, "Keys must not be null"); + Assert.noNullElements(keys, "Keys must not contain null elements"); + + return connection.invoke().just(Jedis::sintercard, PipelineBinaryCommands::sintercard, keys); + } + @Override public Boolean sIsMember(byte @NonNull [] key, byte @NonNull [] value) { diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterSetCommands.java index 2ff5bb3a84..6d67a448bd 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterSetCommands.java @@ -31,6 +31,7 @@ /** * @author Christoph Strobl * @author Mark Paluch + * @author Mingi Lee * @since 2.0 */ class LettuceClusterSetCommands extends LettuceSetCommands { @@ -118,6 +119,21 @@ public Long sInterStore(byte[] destKey, byte[]... keys) { return sAdd(destKey, result.toArray(new byte[result.size()][])); } + @Override + public Long sInterCard(byte[]... keys) { + + Assert.notNull(keys, "Keys must not be null"); + Assert.noNullElements(keys, "Keys must not contain null elements"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(keys)) { + return super.sInterCard(keys); + } + + // For multi-slot clusters, calculate intersection cardinality by performing intersection + Set result = sInter(keys); + return (long) result.size(); + } + @Override public Set sUnion(byte[]... keys) { 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 d74953a6b7..88c9aace76 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 @@ -36,6 +36,7 @@ /** * @author Christoph Strobl * @author Mark Paluch + * @author Mingi Lee * @since 2.0 */ class LettuceReactiveSetCommands implements ReactiveSetCommands { @@ -175,6 +176,18 @@ public Flux> sInterStore(Publisher> sInterCard(Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).concatMap(command -> { + + Assert.notNull(command.getKeys(), "Keys must not be null"); + + return cmd.sintercard(command.getKeys().toArray(new ByteBuffer[0])) + .map(value -> new NumericResponse<>(command, value)); + })); + } + @Override public Flux>> sUnion(Publisher commands) { 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 170592600b..b05afcd41f 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 @@ -39,6 +39,7 @@ /** * @author Christoph Strobl * @author Mark Paluch + * @author Mingi Lee * @since 2.0 */ @NullUnmarked @@ -106,6 +107,15 @@ public Long sInterStore(byte @NonNull [] destKey, byte @NonNull [] @NonNull... k return connection.invoke().just(RedisSetAsyncCommands::sinterstore, destKey, keys); } + @Override + public Long sInterCard(byte @NonNull [] @NonNull... keys) { + + Assert.notNull(keys, "Keys must not be null"); + Assert.noNullElements(keys, "Keys must not contain null elements"); + + return connection.invoke().just(RedisSetAsyncCommands::sintercard, keys); + } + @Override public Boolean sIsMember(byte @NonNull [] key, byte @NonNull [] value) { 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 25905b45d5..3e7fbe4c83 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundSetOperations.java @@ -28,6 +28,7 @@ * * @author Costin Leau * @author Mark Paluch + * @author Mingi Lee */ @NullUnmarked public interface BoundSetOperations extends BoundKeyOperations { @@ -131,6 +132,26 @@ public interface BoundSetOperations extends BoundKeyOperations { */ void intersectAndStore(@NonNull Collection<@NonNull K> keys, @NonNull K destKey); + /** + * Returns the cardinality of the set which would result from the intersection of the bound key and {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Long intersectSize(@NonNull K key); + + /** + * Returns the cardinality of the set which would result from the intersection of the bound key and {@code keys}. + * + * @param keys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Long intersectSize(@NonNull Collection<@NonNull K> keys); + /** * Union all sets at given {@code key} and {@code key}. * 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 33ad054e3b..43055ceba9 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java @@ -41,6 +41,7 @@ * @author Christoph Strobl * @author Roman Bezpalko * @author John Blum + * @author Mingi Lee * @since 2.0 */ class DefaultReactiveSetOperations implements ReactiveSetOperations { @@ -213,6 +214,35 @@ public Mono intersectAndStore(Collection keys, K destKey) { .flatMap(rawKeys -> setCommands.sInterStore(rawKey(destKey), rawKeys))); } + @Override + public Mono intersectSize(K key, K otherKey) { + + Assert.notNull(key, "Key must not be null"); + Assert.notNull(otherKey, "Other key must not be null"); + + return intersectSize(key, Collections.singleton(otherKey)); + } + + @Override + public Mono intersectSize(K key, Collection otherKeys) { + + Assert.notNull(key, "Key must not be null"); + Assert.notNull(otherKeys, "Other keys must not be null"); + + return intersectSize(getKeys(key, otherKeys)); + } + + @Override + public Mono intersectSize(Collection keys) { + + Assert.notNull(keys, "Keys must not be null"); + + return createMono(setCommands -> Flux.fromIterable(keys) // + .map(this::rawKey) // + .collectList() // + .flatMap(setCommands::sInterCard)); + } + @Override public Flux union(K key, K otherKey) { 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 52a73a8840..91590e07c5 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java @@ -33,6 +33,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author Roman Bezpalko + * @author Mingi Lee */ class DefaultSetOperations extends AbstractOperations implements SetOperations { @@ -140,6 +141,25 @@ public Long intersectAndStore(Collection keys, K destKey) { return execute(connection -> connection.sInterStore(rawDestKey, rawKeys)); } + @Override + public Long intersectSize(K key, K otherKey) { + return intersectSize(Arrays.asList(key, otherKey)); + } + + @Override + public Long intersectSize(K key, Collection otherKeys) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + return execute(connection -> connection.sInterCard(rawKeys)); + } + + @Override + public Long intersectSize(Collection keys) { + + byte[][] rawKeys = rawKeys(keys); + return execute(connection -> connection.sInterCard(rawKeys)); + } + @Override public Boolean isMember(K key, Object o) { 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 c3cbad9445..077c35f9ce 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java @@ -33,6 +33,7 @@ * @author Mark Paluch * @author Christoph Strobl * @author Roman Bezpalko + * @author Mingi Lee * @see Redis Documentation: Set Commands * @since 2.0 */ @@ -181,6 +182,38 @@ public interface ReactiveSetOperations { */ Mono intersectAndStore(Collection keys, K destKey); + /** + * Returns the cardinality of the set which would result from the intersection of {@code key} and {@code otherKey}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Mono intersectSize(K key, K otherKey); + + /** + * Returns the cardinality of the set which would result from the intersection of {@code key} and {@code otherKeys}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @return + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Mono intersectSize(K key, Collection otherKeys); + + /** + * Returns the cardinality of the set which would result from the intersection of all given sets at {@code keys}. + * + * @param keys must not be {@literal null}. + * @return + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Mono intersectSize(Collection keys); + /** * Union all sets at given {@code keys} 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 2817069755..78d0f818df 100644 --- a/src/main/java/org/springframework/data/redis/core/SetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/SetOperations.java @@ -30,6 +30,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author Roman Bezpalko + * @author Mingi Lee */ @NullUnmarked public interface SetOperations { @@ -178,6 +179,38 @@ public interface SetOperations { */ Long intersectAndStore(@NonNull Collection<@NonNull K> keys, @NonNull K destKey); + /** + * Returns the cardinality of the set which would result from the intersection of {@code key} and {@code otherKey}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Long intersectSize(@NonNull K key, @NonNull K otherKey); + + /** + * Returns the cardinality of the set which would result from the intersection of {@code key} and {@code otherKeys}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Long intersectSize(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys); + + /** + * Returns the cardinality of the set which would result from the intersection of all given sets at {@code keys}. + * + * @param keys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Long intersectSize(@NonNull Collection<@NonNull K> keys); + /** * Union all sets at given {@code keys} 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 47fd4430a4..7c059a787e 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 @@ -33,6 +33,7 @@ * @author Costin Leau * @author Christoph Strobl * @author Mark Paluch + * @author Mingi Lee */ public class DefaultRedisSet extends AbstractRedisCollection implements RedisSet { @@ -117,6 +118,16 @@ public RedisSet intersectAndStore(Collection> sets, Str return new DefaultRedisSet<>(boundSetOps.getOperations().boundSetOps(destKey)); } + @Override + public Long intersectSize(RedisSet set) { + return boundSetOps.intersectSize(set.getKey()); + } + + @Override + public Long intersectSize(Collection> sets) { + return boundSetOps.intersectSize(CollectionUtils.extractKeys(sets)); + } + @Override public Set union(RedisSet set) { return boundSetOps.union(set.getKey()); 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 50e0592300..d28e724db1 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 @@ -28,6 +28,7 @@ * @author Costin Leau * @author Christoph Strobl * @author Mark Paluch + * @author Mingi Lee */ public interface RedisSet extends RedisCollection, Set { @@ -122,6 +123,28 @@ static RedisSet create(String key, RedisOperations operations) */ RedisSet intersectAndStore(Collection> sets, String destKey); + /** + * Returns the cardinality of the set which would result from the intersection of this set and another + * {@link RedisSet}. + * + * @param set must not be {@literal null}. + * @return the cardinality of the intersection. + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Long intersectSize(RedisSet set); + + /** + * Returns the cardinality of the set which would result from the intersection of this set and other + * {@link RedisSet}s. + * + * @param sets must not be {@literal null}. + * @return the cardinality of the intersection. + * @see Redis Documentation: SINTERCARD + * @since 4.0 + */ + Long intersectSize(Collection> sets); + /** * Get random element from the set. * 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 9cd9e1de31..a852a71b4a 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java @@ -40,6 +40,7 @@ * * @author Mark Paluch * @author Christoph Strobl + * @author Mingi Lee */ @ParameterizedClass @MethodSource("testParams") @@ -230,6 +231,39 @@ void intersectAndStore() { setOperations.isMember(destKey, shared).as(StepVerifier::create).expectNext(true).verifyComplete(); } + @Test + @EnabledOnCommand("SINTERCARD") + void intersectSize() { + + K key = keyFactory.instance(); + K otherKey = keyFactory.instance(); + K thirdKey = keyFactory.instance(); + + V onlyInKey = valueFactory.instance(); + V shared1 = valueFactory.instance(); + V shared2 = valueFactory.instance(); + V onlyInOtherKey = valueFactory.instance(); + + setOperations.add(key, onlyInKey, shared1, shared2).as(StepVerifier::create).expectNext(3L).verifyComplete(); + setOperations.add(otherKey, onlyInOtherKey, shared1, shared2).as(StepVerifier::create).expectNext(3L) + .verifyComplete(); + setOperations.add(thirdKey, shared1).as(StepVerifier::create).expectNext(1L).verifyComplete(); + + // Test intersectSize(key, otherKey) + setOperations.intersectSize(key, otherKey).as(StepVerifier::create).expectNext(2L).verifyComplete(); + + // Test intersectSize(key, Collection) + setOperations.intersectSize(key, Arrays.asList(otherKey)).as(StepVerifier::create).expectNext(2L).verifyComplete(); + + // Test intersectSize(Collection) with multiple keys + setOperations.intersectSize(Arrays.asList(key, otherKey, thirdKey)).as(StepVerifier::create).expectNext(1L) + .verifyComplete(); + + // Test with empty intersection + K emptyKey = keyFactory.instance(); + setOperations.intersectSize(key, emptyKey).as(StepVerifier::create).expectNext(0L).verifyComplete(); + } + @Test // DATAREDIS-602, DATAREDIS-873 void difference() { 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 9844ce228e..9bf1aadfaa 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsIntegrationTests.java @@ -38,6 +38,7 @@ * @author Christoph Strobl * @author Thomas Darimont * @author Mark Paluch + * @author Mingi Lee */ @ParameterizedClass @MethodSource("testParams") @@ -329,6 +330,38 @@ void intersectAndStoreShouldReturnNumberOfElementsInDestination() { assertThat(setOps.intersectAndStore(Arrays.asList(sourceKey1, sourceKey2), destinationKey)).isEqualTo(2L); } + @Test // GH-2906 + @EnabledOnCommand("SINTERCARD") + void intersectSizeShouldReturnIntersectionCardinality() { + + K sourceKey1 = keyFactory.instance(); + K sourceKey2 = keyFactory.instance(); + K sourceKey3 = keyFactory.instance(); + + V v1 = valueFactory.instance(); + V v2 = valueFactory.instance(); + V v3 = valueFactory.instance(); + V v4 = valueFactory.instance(); + V v5 = valueFactory.instance(); + + setOps.add(sourceKey1, v1, v2, v3); + setOps.add(sourceKey2, v2, v3, v4); + setOps.add(sourceKey3, v3, v4, v5); + + // Test two keys intersection + assertThat(setOps.intersectSize(sourceKey1, sourceKey2)).isEqualTo(2L); + + // Test key and collection intersection + assertThat(setOps.intersectSize(sourceKey1, Arrays.asList(sourceKey2, sourceKey3))).isEqualTo(1L); + + // Test collection intersection + assertThat(setOps.intersectSize(Arrays.asList(sourceKey1, sourceKey2, sourceKey3))).isEqualTo(1L); + + // Test empty intersection + K emptyKey = keyFactory.instance(); + assertThat(setOps.intersectSize(sourceKey1, emptyKey)).isEqualTo(0L); + } + @Test // GH-2037 @EnabledOnCommand("SMISMEMBER") void isMember() {