From 4445d2d6be7ae91f3d759b4a203dcb28ff33202a Mon Sep 17 00:00:00 2001 From: Kiminni Date: Sun, 31 Aug 2025 19:22:49 +0900 Subject: [PATCH 01/12] Add SINTERCARD command to RedisSetCommands Signed-off-by: Kiminni --- .../data/redis/connection/RedisSetCommands.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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..8c284853c6 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 3.4 + */ + Long sInterCard(byte @NonNull [] @NonNull... keys); + /** * Union all sets at given {@code keys}. * From 4616f7e31552552fcc06060d2918fb9ed60e74ea Mon Sep 17 00:00:00 2001 From: Kiminni Date: Sun, 31 Aug 2025 19:22:58 +0900 Subject: [PATCH 02/12] Add SINTERCARD operation to Jedis and Lettuce set commands Signed-off-by: Kiminni --- .../data/redis/connection/jedis/JedisSetCommands.java | 10 ++++++++++ .../redis/connection/lettuce/LettuceSetCommands.java | 10 ++++++++++ 2 files changed, 20 insertions(+) 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/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) { From 3e901aa3a71b0c5ddeddb4409df3529608c2873a Mon Sep 17 00:00:00 2001 From: Kiminni Date: Sun, 31 Aug 2025 19:23:20 +0900 Subject: [PATCH 03/12] Add SINTERCARD operation to Jedis and Lettuce set commands Signed-off-by: Kiminni --- .../connection/DefaultStringRedisConnection.java | 11 +++++++++++ .../redis/connection/DefaultedRedisConnection.java | 8 ++++++++ .../data/redis/connection/StringRedisConnection.java | 12 ++++++++++++ 3 files changed, 31 insertions(+) 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/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java index 9e0392e46d..55d6dcd392 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 3.4 + */ + Long sInterCard(@NonNull String @NonNull... keys); + /** * Union all sets at given {@code keys}. * From 718ac9d04026a32e007989836f697d2e18065014 Mon Sep 17 00:00:00 2001 From: Kiminni Date: Sun, 31 Aug 2025 19:26:49 +0900 Subject: [PATCH 04/12] Add intersectSize methods for SINTERCARD operation in SetOperations Signed-off-by: Kiminni --- .../data/redis/core/DefaultSetOperations.java | 19 +++++++++++ .../data/redis/core/SetOperations.java | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+) 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..660fdb4cb5 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java @@ -140,6 +140,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/SetOperations.java b/src/main/java/org/springframework/data/redis/core/SetOperations.java index 2817069755..086c36bc1e 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 3.4 + */ + 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 3.4 + */ + 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 3.4 + */ + Long intersectSize(@NonNull Collection<@NonNull K> keys); + /** * Union all sets at given {@code keys} and {@code otherKey}. * From 48cc51e6b83a29f2a439d163bfd7e7822584d935 Mon Sep 17 00:00:00 2001 From: Kiminni Date: Sun, 31 Aug 2025 19:38:20 +0900 Subject: [PATCH 05/12] Add sInterCard methods to JedisClusterSetCommands and LettuceClusterSetCommands Signed-off-by: Kiminni --- .../jedis/JedisClusterSetCommands.java | 20 +++++++++++++++++++ .../lettuce/LettuceClusterSetCommands.java | 16 +++++++++++++++ 2 files changed, 36 insertions(+) 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/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) { From 4fc196547c1caf588f2c589efd391df366b0d12d Mon Sep 17 00:00:00 2001 From: Kiminni Date: Sun, 31 Aug 2025 19:38:30 +0900 Subject: [PATCH 06/12] Add intersectSize test for SINTERCARD operation in DefaultSetOperationsIntegrationTests Signed-off-by: Kiminni --- .../DefaultSetOperationsIntegrationTests.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) 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() { From 32075fd431feb7553f99f0d1f8299ffc77b18c25 Mon Sep 17 00:00:00 2001 From: Kiminni Date: Wed, 1 Oct 2025 07:40:14 +0900 Subject: [PATCH 07/12] Update @since tags from 3.4 to 4.0 for SINTERCARD operations Signed-off-by: Kiminni --- .../data/redis/connection/RedisSetCommands.java | 2 +- .../data/redis/connection/StringRedisConnection.java | 2 +- .../org/springframework/data/redis/core/SetOperations.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) 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 8c284853c6..dd1bb725ab 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java @@ -160,7 +160,7 @@ public interface RedisSetCommands { * @param keys must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SINTERCARD - * @since 3.4 + * @since 4.0 */ Long sInterCard(byte @NonNull [] @NonNull... 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 55d6dcd392..f49dc1a61f 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -1177,7 +1177,7 @@ String bLMove(@NonNull String sourceKey, @NonNull String destinationKey, @NonNul * @return * @see Redis Documentation: SINTERCARD * @see RedisSetCommands#sInterCard(byte[]...) - * @since 3.4 + * @since 4.0 */ Long sInterCard(@NonNull String @NonNull... keys); 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 086c36bc1e..78d0f818df 100644 --- a/src/main/java/org/springframework/data/redis/core/SetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/SetOperations.java @@ -186,7 +186,7 @@ public interface SetOperations { * @param otherKey must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SINTERCARD - * @since 3.4 + * @since 4.0 */ Long intersectSize(@NonNull K key, @NonNull K otherKey); @@ -197,7 +197,7 @@ public interface SetOperations { * @param otherKeys must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SINTERCARD - * @since 3.4 + * @since 4.0 */ Long intersectSize(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys); @@ -207,7 +207,7 @@ public interface SetOperations { * @param keys must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SINTERCARD - * @since 3.4 + * @since 4.0 */ Long intersectSize(@NonNull Collection<@NonNull K> keys); From a58f2f8f73c533ba5683a67fbef162513bb967d1 Mon Sep 17 00:00:00 2001 From: Kiminni Date: Wed, 1 Oct 2025 07:46:33 +0900 Subject: [PATCH 08/12] Add intersectSize methods for SINTERCARD operation in BoundSetOperations Signed-off-by: Kiminni --- .../data/redis/core/BoundSetOperations.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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}. * From 5b32dee00bbea389adb99446d532c44f8bd35c51 Mon Sep 17 00:00:00 2001 From: Kiminni Date: Wed, 1 Oct 2025 10:53:25 +0900 Subject: [PATCH 09/12] Add intersectSize methods for SINTERCARD operation in Reactive APIs Signed-off-by: Kiminni --- .../redis/connection/ReactiveSetCommands.java | 68 +++++++++++++++++++ .../lettuce/LettuceReactiveSetCommands.java | 13 ++++ .../core/DefaultReactiveSetOperations.java | 30 ++++++++ .../redis/core/ReactiveSetOperations.java | 33 +++++++++ 4 files changed, 144 insertions(+) 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/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/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/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}. * From bebdda7bfb2716c7d5d0d97ca4f9f5f3e50c7f9e Mon Sep 17 00:00:00 2001 From: Kiminni Date: Wed, 1 Oct 2025 14:19:45 +0900 Subject: [PATCH 10/12] Add intersectSize test for SINTERCARD operation in DefaultReactiveSetOperationsIntegrationTests Signed-off-by: Kiminni --- ...ReactiveSetOperationsIntegrationTests.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) 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() { From cd23f4bf724379e191306c74e6be0ea04d549d46 Mon Sep 17 00:00:00 2001 From: Kiminni Date: Wed, 1 Oct 2025 14:19:56 +0900 Subject: [PATCH 11/12] Add intersectSize methods for SINTERCARD operation in RedisSet Signed-off-by: Kiminni --- .../support/collections/DefaultRedisSet.java | 11 +++++++++ .../redis/support/collections/RedisSet.java | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+) 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. * From 691ecfc628d5829659124d854a29c51e1a0c0f19 Mon Sep 17 00:00:00 2001 From: Kiminni Date: Wed, 1 Oct 2025 14:26:03 +0900 Subject: [PATCH 12/12] Add missing @author tag to DefaultSetOperations Signed-off-by: Kiminni --- .../springframework/data/redis/core/DefaultSetOperations.java | 1 + 1 file changed, 1 insertion(+) 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 660fdb4cb5..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 {