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 ad35115b73..fb08f071a8 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -1084,6 +1084,16 @@ public Long zInterStore(byte[] destKey, byte[]... sets) { return convertAndReturn(delegate.zInterStore(destKey, sets), Converters.identityConverter()); } + @Override + public Long zInterCard(byte[]... sets) { + return convertAndReturn(delegate.zInterCard(sets), Converters.identityConverter()); + } + + @Override + public Long zInterCard(long limit, byte[]... sets) { + return convertAndReturn(delegate.zInterCard(limit, sets), Converters.identityConverter()); + } + @Override public Set zRange(byte[] key, long start, long end) { return convertAndReturn(delegate.zRange(key, start, end), Converters.identityConverter()); @@ -2089,6 +2099,11 @@ public Long zInterStore(String destKey, String... sets) { return zInterStore(serialize(destKey), serializeMulti(sets)); } + @Override + public Long zInterCard(String... keys) { + return zInterCard(serializeMulti(keys)); + } + @Override public byte[] zRandMember(byte[] key) { return delegate.zRandMember(key); 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 dd02f85661..0e86a7deef 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -1146,6 +1146,20 @@ default Long zInterStore(byte[] destKey, byte[]... sets) { return zSetCommands().zInterStore(destKey, sets); } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Long zInterCard(byte[]... sets) { + return zSetCommands().zInterCard(sets); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Long zInterCard(long limit, byte[]... sets) { + return zSetCommands().zInterCard(limit, sets); + } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java index 5e9b4de22c..16ac16c799 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java @@ -3026,6 +3026,72 @@ default Mono zInterStore(ByteBuffer destinationKey, List sets, */ Flux> zInterStore(Publisher commands); + /** + * {@code ZINTERCARD} command parameters. + * + * @author GyeongHoe Koo + * @since 4.0 + * @see Redis Documentation: ZINTERCARD + */ + class ZInterCardCommand implements Command { + + private final List keys; + + private ZInterCardCommand(List keys) { + this.keys = keys; + } + + /** + * Creates a new {@link ZInterCardCommand} given a {@link Collection} of keys. + * + * @param keys must not be {@literal null}. + * @return a new {@link ZInterCardCommand} for a {@link Collection} of keys. + */ + public static ZInterCardCommand keys(Collection keys) { + + Assert.notNull(keys, "Keys must not be null"); + + return new ZInterCardCommand(new ArrayList<>(keys)); + } + + @Override + public @Nullable ByteBuffer getKey() { + return null; + } + + /** + * @return never {@literal null}. + */ + public List getKeys() { + return keys; + } + } + + /** + * Get the number of elements in the intersection of all given sorted sets. + * + * @param keys must not be {@literal null}. + * @return + * @see Redis Documentation: ZINTERCARD + * @since 4.0 + */ + default Mono zInterCard(List keys) { + + Assert.notNull(keys, "Keys must not be null"); + + return zInterCard(Mono.just(ZInterCardCommand.keys(keys))).next().map(NumericResponse::getOutput); + } + + /** + * Get the number of elements in the intersection of all given sorted sets. + * + * @param commands must not be {@literal null}. + * @return + * @see Redis Documentation: ZINTERCARD + * @since 4.0 + */ + Flux> zInterCard(Publisher commands); + /** * Union sorted {@literal sets}. * diff --git a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java index 2518918b09..716bf6ccc2 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java @@ -42,6 +42,7 @@ * @author Mark Paluch * @author Andrey Shlykov * @author Shyngys Sapraliyev + * @author GyeongHoe Koo * @see RedisCommands */ @NullUnmarked @@ -1085,6 +1086,28 @@ default Long zInterStore(byte @NonNull [] destKey, @NonNull Aggregate aggregate, Long zInterStore(byte @NonNull [] destKey, @NonNull Aggregate aggregate, @NonNull Weights weights, byte @NonNull [] @NonNull... sets); + /** + * Get the number of members in the intersection of sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 4.0 + * @see Redis Documentation: ZINTERCARD + */ + Long zInterCard(byte @NonNull [] @NonNull... sets); + + /** + * Get the number of members in the intersection of sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @param limit the maximum cardinality to compute. If the intersection has more than {@code limit} elements, + * the computation stops and returns {@code limit}. + * @return {@literal null} when used in pipeline / transaction. + * @since 4.0 + * @see Redis Documentation: ZINTERCARD + */ + Long zInterCard(long limit, byte @NonNull [] @NonNull... sets); + /** * Union sorted {@code sets}. * 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 8b4a3c4302..0cd77fe958 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -1837,6 +1837,17 @@ Set zInterWithScores(@NonNull Aggregate aggregate, @NonNull Weights Long zInterStore(@NonNull String destKey, @NonNull Aggregate aggregate, int @NonNull [] weights, @NonNull String @NonNull... sets); + /** + * Get the number of elements in the intersection of the sorted sets at the given {@code keys}. + * + * @param keys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 4.0 + * @see Redis Documentation: ZINTERCARD + * @see RedisZSetCommands#zInterCard(byte[]...) + */ + Long zInterCard(@NonNull String @NonNull... keys); + /** * Union sorted {@code sets}. * diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java index 352230a8b6..0c2990b6ea 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java @@ -1086,6 +1086,42 @@ public Long zUnionStore(byte @NonNull [] destKey, @NonNull Aggregate aggregate, throw new InvalidDataAccessApiUsageException("ZUNIONSTORE can only be executed when all keys map to the same slot"); } + @Override + public Long zInterCard(byte @NonNull [] @NonNull... sets) { + + Assert.notNull(sets, "Sets must not be null"); + Assert.noNullElements(sets, "Sets must not contain null elements"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return connection.getCluster().zintercard(sets); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZINTERCARD can only be executed when all keys map to the same slot"); + } + + @Override + public Long zInterCard(long limit, byte @NonNull [] @NonNull... sets) { + + Assert.notNull(sets, "Sets must not be null"); + Assert.noNullElements(sets, "Sets must not contain null elements"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return connection.getCluster().zintercard(limit, sets); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZINTERCARD can only be executed when all keys map to the same slot"); + } + @Override public Cursor<@NonNull Tuple> zScan(byte @NonNull [] key, @NonNull ScanOptions options) { diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java index a90cfbde86..b7d6c63600 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java @@ -564,6 +564,24 @@ public Long zUnionStore(byte @NonNull [] destKey, byte @NonNull [] @NonNull... s return connection.invoke().just(Jedis::zunionstore, PipelineBinaryCommands::zunionstore, destKey, sets); } + @Override + public Long zInterCard(byte @NonNull [] @NonNull... sets) { + + Assert.notNull(sets, "Sets must not be null"); + Assert.noNullElements(sets, "Sets must not contain null elements"); + + return connection.invoke().just(Jedis::zintercard, PipelineBinaryCommands::zintercard, sets); + } + + @Override + public Long zInterCard(long limit, byte @NonNull [] @NonNull... sets) { + + Assert.notNull(sets, "Sets must not be null"); + Assert.noNullElements(sets, "Sets must not contain null elements"); + + return connection.invoke().just(Jedis::zintercard, PipelineBinaryCommands::zintercard, limit, sets); + } + @Override public Cursor<@NonNull Tuple> zScan(byte @NonNull [] key, @NonNull ScanOptions options) { return zScan(key, CursorId.initial(), options); diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java index 4b24d0319e..2933dab0e8 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java @@ -624,6 +624,19 @@ public Flux> zInterStore( })); } + @Override + public Flux> zInterCard(Publisher commands) { + + return this.connection.execute(reactiveCommands -> Flux.from(commands).concatMap(command -> { + + Assert.notEmpty(command.getKeys(), "Keys must not be null or empty"); + + ByteBuffer[] keys = command.getKeys().toArray(new ByteBuffer[0]); + + return reactiveCommands.zintercard(keys).map(value -> new NumericResponse<>(command, value)); + })); + } + @Override public Flux>> zUnion( Publisher commands) { diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java index e1a02dff78..15b96e9cc0 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java @@ -532,6 +532,24 @@ public Long zUnionStore(byte @NonNull [] destKey, byte @NonNull [] @NonNull... s return connection.invoke().just(RedisSortedSetAsyncCommands::zunionstore, destKey, sets); } + @Override + public Long zInterCard(byte @NonNull [] @NonNull... sets) { + + Assert.notNull(sets, "Sets must not be null"); + Assert.noNullElements(sets, "Sets must not contain null elements"); + + return connection.invoke().just(RedisSortedSetAsyncCommands::zintercard, sets); + } + + @Override + public Long zInterCard(long limit, byte @NonNull [] @NonNull... sets) { + + Assert.notNull(sets, "Sets must not be null"); + Assert.noNullElements(sets, "Sets must not contain null elements"); + + return connection.invoke().just(RedisSortedSetAsyncCommands::zintercard, limit, sets); + } + @Override public Cursor<@NonNull Tuple> zScan(byte @NonNull [] key, @Nullable ScanOptions options) { return zScan(key, CursorId.initial(), options != null ? options : ScanOptions.NONE); diff --git a/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java index baab58030b..ddf578ecbf 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java @@ -585,6 +585,18 @@ public Mono intersectAndStore(K key, Collection otherKeys, K destKey, A .flatMap(serialized -> zSetCommands.zInterStore(rawKey(destKey), serialized, weights, aggregate))); } + @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 createMono(zSetCommands -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMap(zSetCommands::zInterCard)); + } + @Override public Flux union(K key, Collection otherKeys) { diff --git a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java index be4573e890..70057c4b0c 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java @@ -46,6 +46,7 @@ * @author Shyngys Sapraliyev * @author John Blum * @author Gunha Hwang + * @author GyeongHoe Koo */ @NullUnmarked class DefaultZSetOperations extends AbstractOperations implements ZSetOperations { @@ -588,6 +589,22 @@ public Long intersectAndStore(@NonNull K key, Collection<@NonNull K> otherKeys, return execute(connection -> connection.zInterStore(rawDestKey, aggregate, weights, rawKeys)); } + @Override + public Long intersectSize(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + + return execute(connection -> connection.zInterCard(rawKeys)); + } + + @Override + public Long intersectSize(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys, long limit) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + + return execute(connection -> connection.zInterCard(limit, rawKeys)); + } + @Override public Set union(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys) { diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java index a8083ec252..48a8162126 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java @@ -823,6 +823,32 @@ default Mono intersectAndStore(@NonNull K key, @NonNull Collection<@NonNul Mono intersectAndStore(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys, @NonNull K destKey, @NonNull Aggregate aggregate, @NonNull Weights weights); + /** + * Returns the cardinality of the sorted 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: ZINTERCARD + * @since 4.0 + */ + default Mono intersectSize(@NonNull K key, @NonNull K otherKey) { + return intersectSize(key, Collections.singleton(otherKey)); + } + + /** + * Returns the cardinality of the sorted 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: ZINTERCARD + * @since 4.0 + */ + Mono intersectSize(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys); + /** * Union sorted {@code sets}. * diff --git a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java index c5a640bb87..fd906fbcbe 100644 --- a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java @@ -44,6 +44,7 @@ * @author Andrey Shlykov * @author Shyngys Sapraliyev * @author Gunha Hwang + * @author GyeongHoe Koo */ @NullUnmarked public interface ZSetOperations { @@ -766,6 +767,40 @@ default Long intersectAndStore(@NonNull K key, @NonNull Collection<@NonNull K> o Long intersectAndStore(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys, @NonNull K destKey, @NonNull Aggregate aggregate, @NonNull Weights weights); + /** + * Returns numbers of members in the sorted set resulting from the intersection of the sorted sets stored at {@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: ZINTERCARD + */ + default Long intersectSize(@NonNull K key, @NonNull K otherKey) { + return intersectSize(key, Collections.singleton(otherKey)); + } + + /** + * Returns numbers of members in the sorted set resulting from the intersection of the sorted sets stored at {@code key} and {@code otherKey}. + * + * @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: ZINTERCARD + */ + Long intersectSize(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys); + + /** + * Returns numbers of members in the sorted set resulting from the intersection of the sorted sets stored at {@code key} and {@code otherKey}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @param limit the maximum cardinality to compute. If the intersection has more than {@code limit} elements, + * the computation stops and returns {@code limit}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZINTERCARD + */ + Long intersectSize(@NonNull K key, @NonNull Collection<@NonNull K> otherKeys, long limit); + /** * Union sorted {@code sets}. * 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 d09bb78ddf..95a87ddead 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -2192,6 +2192,34 @@ void testZInterStoreAggWeights() { new DefaultStringTuple("James".getBytes(), "James", 12d))) })); } + @Test // GH-3253 + @EnabledOnCommand("ZINTERCARD") + void testZInterCard() { + + actual.add(connection.zAdd("myset", 2, "Bob")); + actual.add(connection.zAdd("myset", 1, "James")); + actual.add(connection.zAdd("myset", 4, "Joe")); + actual.add(connection.zAdd("otherset", 1, "Bob")); + actual.add(connection.zAdd("otherset", 4, "James")); + actual.add(connection.zInterCard("myset", "otherset")); + verifyResults(Arrays.asList(new Object[] { true, true, true, true, true, 2L })); + } + + @Test // GH-3253 + @EnabledOnCommand("ZINTERCARD") + void testZInterCardMultipleKeys() { + + actual.add(connection.zAdd("myset", 2, "Bob")); + actual.add(connection.zAdd("myset", 1, "James")); + actual.add(connection.zAdd("myset", 4, "Joe")); + actual.add(connection.zAdd("otherset", 1, "Bob")); + actual.add(connection.zAdd("otherset", 4, "James")); + actual.add(connection.zAdd("thirdset", 2, "Bob")); + actual.add(connection.zAdd("thirdset", 3, "Joe")); + actual.add(connection.zInterCard("myset", "otherset", "thirdset")); + verifyResults(Arrays.asList(new Object[] { true, true, true, true, true, true, true, 1L })); + } + @Test // GH-2049 @EnabledOnCommand("ZRANDMEMBER") void testZRandMember() { 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 08ee13c910..fd62093c21 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 @@ -2743,6 +2743,26 @@ public void zInterStoreShouldWorkForSameSlotKeys() { assertThat(nativeConnection.zrange(SAME_SLOT_KEY_3_BYTES, 0, -1)).contains(VALUE_2_BYTES); } + @Test // GH-3253 + public void zInterCardShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { + + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zInterCard(KEY_1_BYTES, KEY_2_BYTES)); + } + + @Test // GH-3253 + @EnabledOnCommand("ZINTERCARD") + public void zInterCardShouldWorkForSameSlotKeys() { + + nativeConnection.zadd(SAME_SLOT_KEY_1_BYTES, 10D, VALUE_1_BYTES); + nativeConnection.zadd(SAME_SLOT_KEY_1_BYTES, 20D, VALUE_2_BYTES); + + nativeConnection.zadd(SAME_SLOT_KEY_2_BYTES, 20D, VALUE_2_BYTES); + nativeConnection.zadd(SAME_SLOT_KEY_2_BYTES, 30D, VALUE_3_BYTES); + + assertThat(clusterConnection.zInterCard(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).isEqualTo(1L); + } + @Test // GH-2007 @EnabledOnCommand("ZPOPMIN") public void zPopMinShouldWorkCorrectly() { 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 af6285dad9..cf75582c29 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 @@ -2823,6 +2823,26 @@ public void zInterStoreShouldWorkForSameSlotKeys() { assertThat(nativeConnection.zrange(SAME_SLOT_KEY_3, 0, -1)).contains(VALUE_2); } + @Test // GH-3253 + public void zInterCardShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { + + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zInterCard(KEY_1_BYTES, KEY_2_BYTES)); + } + + @Test // GH-3253 + @EnabledOnCommand("ZINTERCARD") + public void zInterCardShouldWorkForSameSlotKeys() { + + nativeConnection.zadd(SAME_SLOT_KEY_1, 10D, VALUE_1); + nativeConnection.zadd(SAME_SLOT_KEY_1, 20D, VALUE_2); + + nativeConnection.zadd(SAME_SLOT_KEY_2, 20D, VALUE_2); + nativeConnection.zadd(SAME_SLOT_KEY_2, 30D, VALUE_3); + + assertThat(clusterConnection.zInterCard(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).isEqualTo(1L); + } + @Test // GH-2007 @EnabledOnCommand("ZPOPMIN") public void zPopMinShouldWorkCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java index 9696b00801..9dd70eee37 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java @@ -791,6 +791,24 @@ void zInterStoreShouldWorkCorrectly() { .isEqualTo(2L); } + @Test // GH-3253 + @EnabledOnCommand("ZINTERCARD") + void zInterCardShouldWorkCorrectly() { + + assumeThat(nativeCommands).isInstanceOf(io.lettuce.core.api.sync.RedisCommands.class); + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + nativeCommands.zadd(KEY_2, 1D, VALUE_1); + nativeCommands.zadd(KEY_2, 2D, VALUE_2); + nativeCommands.zadd(KEY_2, 3D, VALUE_3); + + connection.zSetCommands().zInterCard(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER)) // + .as(StepVerifier::create) // + .expectNext(2L) // + .verifyComplete(); + } + @Test // GH-2042 void zUnionShouldWorkCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java index ec60bd744e..8bf59d3a52 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java @@ -824,6 +824,32 @@ void intersectAndStoreWithAggregation() { .verifyComplete(); } + @Test // GH-3253 + @EnabledOnCommand("ZINTERCARD") + void intersectSize() { + + K key = keyFactory.instance(); + K otherKey = keyFactory.instance(); + + V onlyInKey = valueFactory.instance(); + V shared1 = valueFactory.instance(); + V shared2 = valueFactory.instance(); + V onlyInOtherKey = valueFactory.instance(); + + zSetOperations.add(key, onlyInKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, shared1, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, shared2, 12).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.add(otherKey, shared1, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(otherKey, shared2, 12).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(otherKey, onlyInOtherKey, 13).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.intersectSize(key, otherKey).as(StepVerifier::create).expectNext(2L).verifyComplete(); + + zSetOperations.intersectSize(key, Collections.singleton(otherKey)).as(StepVerifier::create).expectNext(2L) + .verifyComplete(); + } + @Test // GH-2042 @EnabledOnCommand("ZUNION") void union() { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java index 0fe579b153..f141bf5e78 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -608,6 +609,55 @@ void testZsetIntersectWithAggregateWeights() { assertThat(zSetOps.score(key1, value1)).isCloseTo(6.0, offset(0.1)); } + @Test // GH-3253 + @EnabledOnCommand("ZINTERCARD") + void testZsetIntersectSize() { + + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + K key3 = keyFactory.instance(); + + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + + zSetOps.add(key1, value1, 1.0); + zSetOps.add(key1, value2, 2.0); + zSetOps.add(key1, value3, 3.0); + + zSetOps.add(key2, value2, 4.0); + zSetOps.add(key2, value3, 5.0); + + zSetOps.add(key3, value3, 6.0); + + // Test basic intersectSize + assertThat(zSetOps.intersectSize(key1, key2)).isEqualTo(2L); + assertThat(zSetOps.intersectSize(key1, Collections.singletonList(key2))).isEqualTo(2L); + + // Test with 3 sets + assertThat(zSetOps.intersectSize(key1, List.of(key2, key3))).isEqualTo(1L); + + // Test with limit + assertThat(zSetOps.intersectSize(key1, Collections.singletonList(key2), 1L)).isEqualTo(1L); + } + + @Test // GH-3253 + @EnabledOnCommand("ZINTERCARD") + void testZsetIntersectSizeEmptySet() { + + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + zSetOps.add(key1, value1, 1.0); + zSetOps.add(key2, value2, 2.0); + + // No intersection + assertThat(zSetOps.intersectSize(key1, key2)).isEqualTo(0L); + } + @Test // GH-2042 @EnabledOnCommand("ZUNION") void testZsetUnion() {