diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 28d9cc7f1..920916f20 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`). +* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`, `COPY`, `GETEX`, `GETDEL`, `ZPOPMIN`, `BZPOPMIN`, `ZPOPMAX`, `BZPOPMAX`, `ZMSCORE`, `ZDIFF`, `ZDIFFSTORE`, `ZINTER`, `ZUNION`). [[new-in-2.5.0]] == New in Spring Data Redis 2.5 diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index 063975165..34222cb93 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -23,6 +23,7 @@ import java.util.function.IntFunction; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.core.convert.converter.Converter; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Distance; @@ -278,7 +279,6 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.decrBy(key, value), Converters.identityConverter()); } - /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#del(byte[][]) @@ -1453,6 +1453,127 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.zIncrBy(key, increment, value), Converters.identityConverter()); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiff(byte[][]) + */ + @Nullable + @Override + public Set zDiff(byte[]... sets) { + return convertAndReturn(delegate.zDiff(sets), Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffWithScores(byte[][]) + */ + @Nullable + @Override + public Set zDiffWithScores(byte[]... sets) { + return convertAndReturn(delegate.zDiffWithScores(sets), Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffStore(byte[], byte[][]) + */ + @Nullable + @Override + public Long zDiffStore(byte[] destKey, byte[]... sets) { + return convertAndReturn(delegate.zDiffStore(destKey, sets), Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zDiff(java.lang.String[]) + */ + @Nullable + @Override + public Set zDiff(String... sets) { + return convertAndReturn(delegate.zDiff(serializeMulti(sets)), byteSetToStringSet); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zDiffWithScores(java.lang.String[]) + */ + @Nullable + @Override + public Set zDiffWithScores(String... sets) { + return convertAndReturn(delegate.zDiffWithScores(serializeMulti(sets)), tupleToStringTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zDiffStore(java.lang.String, java.lang.String[]) + */ + @Nullable + @Override + public Long zDiffStore(String destKey, String... sets) { + return convertAndReturn(delegate.zDiffStore(serialize(destKey), serializeMulti(sets)), + Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInter(byte[][]) + */ + @Nullable + @Override + public Set zInter(byte[]... sets) { + return convertAndReturn(delegate.zInter(sets), Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterWithScores(byte[][]) + */ + @Nullable + @Override + public Set zInterWithScores(byte[]... sets) { + return convertAndReturn(delegate.zInterWithScores(sets), Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Nullable + @Override + public Set zInterWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + return convertAndReturn(delegate.zInterWithScores(aggregate, weights, sets), Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zInter(java.lang.String[]) + */ + @Nullable + @Override + public Set zInter(String... sets) { + return convertAndReturn(delegate.zInter(serializeMulti(sets)), byteSetToStringSet); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zInterWithScores(java.lang.String[]) + */ + @Nullable + @Override + public Set zInterWithScores(String... sets) { + return convertAndReturn(delegate.zInterWithScores(serializeMulti(sets)), tupleToStringTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zInterWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, java.lang.String[]) + */ + @Nullable + @Override + public Set zInterWithScores(Aggregate aggregate, Weights weights, String... sets) { + return convertAndReturn(delegate.zInterWithScores(aggregate, weights, serializeMulti(sets)), tupleToStringTuple); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) @@ -1531,7 +1652,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco */ @Override public Set zRangeByScoreWithScores(byte[] key, double min, double max, long offset, long count) { - return convertAndReturn(delegate.zRangeByScoreWithScores(key, min, max, offset, count), Converters.identityConverter()); + return convertAndReturn(delegate.zRangeByScoreWithScores(key, min, max, offset, count), + Converters.identityConverter()); } /* @@ -1603,7 +1725,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco */ @Override public Set zRevRangeByScoreWithScores(byte[] key, double min, double max, long offset, long count) { - return convertAndReturn(delegate.zRevRangeByScoreWithScores(key, min, max, offset, count), Converters.identityConverter()); + return convertAndReturn(delegate.zRevRangeByScoreWithScores(key, min, max, offset, count), + Converters.identityConverter()); } /* @@ -1732,6 +1855,66 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.zMScore(key, values), Converters.identityConverter()); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnion(byte[][]) + */ + @Nullable + @Override + public Set zUnion(byte[]... sets) { + return convertAndReturn(delegate.zUnion(sets), Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionWithScores(byte[][]) + */ + @Nullable + @Override + public Set zUnionWithScores(byte[]... sets) { + return convertAndReturn(delegate.zUnionWithScores(sets), Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Nullable + @Override + public Set zUnionWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + return convertAndReturn(delegate.zUnionWithScores(aggregate, weights, sets), Converters.identityConverter()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zUnion(java.lang.String[]) + */ + @Nullable + @Override + public Set zUnion(String... sets) { + return convertAndReturn(delegate.zUnion(serializeMulti(sets)), byteSetToStringSet); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zUnionWithScores(java.lang.String[]) + */ + @Nullable + @Override + public Set zUnionWithScores(String... sets) { + return convertAndReturn(delegate.zUnionWithScores(serializeMulti(sets)), tupleToStringTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zUnionWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, java.lang.String[]) + */ + @Nullable + @Override + public Set zUnionWithScores(Aggregate aggregate, Weights weights, String... sets) { + return convertAndReturn(delegate.zUnionWithScores(aggregate, weights, serializeMulti(sets)), tupleToStringTuple); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) @@ -1863,7 +2046,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco */ @Override public T evalSha(String scriptSha1, ReturnType returnType, int numKeys, byte[]... keysAndArgs) { - return convertAndReturn(delegate.evalSha(scriptSha1, returnType, numKeys, keysAndArgs), Converters.identityConverter()); + return convertAndReturn(delegate.evalSha(scriptSha1, returnType, numKeys, keysAndArgs), + Converters.identityConverter()); } /* @@ -1872,7 +2056,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco */ @Override public T evalSha(byte[] scriptSha1, ReturnType returnType, int numKeys, byte[]... keysAndArgs) { - return convertAndReturn(delegate.evalSha(scriptSha1, returnType, numKeys, keysAndArgs), Converters.identityConverter()); + return convertAndReturn(delegate.evalSha(scriptSha1, returnType, numKeys, keysAndArgs), + Converters.identityConverter()); } // @@ -1959,6 +2144,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco public Boolean copy(String sourceKey, String targetKey, boolean replace) { return copy(serialize(sourceKey), serialize(targetKey), replace); } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#decr(java.lang.String) @@ -1986,7 +2172,6 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return del(serializeMulti(keys)); } - /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#unlink(java.lang.String[]) @@ -3999,7 +4184,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco */ @Override public List xClaimJustId(String key, String group, String consumer, XClaimOptions options) { - return convertAndReturn(delegate.xClaimJustId(serialize(key), group, consumer, options), Converters.identityConverter()); + return convertAndReturn(delegate.xClaimJustId(serialize(key), group, consumer, options), + Converters.identityConverter()); } /* @@ -4036,7 +4222,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco */ @Override public String xGroupCreate(String key, ReadOffset readOffset, String group, boolean mkStream) { - return convertAndReturn(delegate.xGroupCreate(serialize(key), group, readOffset, mkStream), Converters.identityConverter()); + return convertAndReturn(delegate.xGroupCreate(serialize(key), group, readOffset, mkStream), + Converters.identityConverter()); } /* @@ -4109,7 +4296,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco @Override public PendingMessages xPending(String key, String groupName, String consumer, org.springframework.data.domain.Range range, Long count) { - return convertAndReturn(delegate.xPending(serialize(key), groupName, consumer, range, count), Converters.identityConverter()); + return convertAndReturn(delegate.xPending(serialize(key), groupName, consumer, range, count), + Converters.identityConverter()); } /* @@ -4406,7 +4594,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco } return value == null ? null - : ObjectUtils.nullSafeEquals(converter, Converters.identityConverter()) ? (T) value : (T) converter.convert(value); + : ObjectUtils.nullSafeEquals(converter, Converters.identityConverter()) ? (T) value + : (T) converter.convert(value); } private void addResultConverter(Converter converter) { 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 cf51c4141..d7b2f346a 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -991,6 +991,27 @@ public interface DefaultedRedisConnection extends RedisConnection { return zSetCommands().zCount(key, range); } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zDiff(byte[]... sets) { + return zSetCommands().zDiff(sets); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zDiffWithScores(byte[]... sets) { + return zSetCommands().zDiffWithScores(sets); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Long zDiffStore(byte[] destKey, byte[]... sets) { + return zSetCommands().zDiffStore(destKey, sets); + } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated @@ -998,6 +1019,34 @@ public interface DefaultedRedisConnection extends RedisConnection { return zSetCommands().zIncrBy(key, increment, value); } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zInter(byte[]... sets) { + return zSetCommands().zInter(sets); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zInterWithScores(Aggregate aggregate, int[] weights, byte[]... sets) { + return zSetCommands().zInterWithScores(aggregate, weights, sets); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zInterWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + return zSetCommands().zInterWithScores(aggregate, weights, sets); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zInterWithScores(byte[]... sets) { + return zSetCommands().zInterWithScores(sets); + } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated @@ -1152,6 +1201,34 @@ public interface DefaultedRedisConnection extends RedisConnection { return zSetCommands().zMScore(key, values); } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zUnion(byte[]... sets) { + return zSetCommands().zUnion(sets); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zUnionWithScores(Aggregate aggregate, int[] weights, byte[]... sets) { + return zSetCommands().zUnionWithScores(aggregate, weights, sets); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zUnionWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + return zSetCommands().zUnionWithScores(aggregate, weights, sets); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zUnionWithScores(byte[]... sets) { + return zSetCommands().zUnionWithScores(sets); + } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated 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 951499cf7..e9fb5765b 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,27 @@ public interface RedisSetCommands { @Nullable Boolean sIsMember(byte[] key, byte[] value); + /** + * Diff all sets for given {@code keys}. + * + * @param keys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SDIFF + */ + @Nullable + Set sDiff(byte[]... keys); + + /** + * Diff all sets for given {@code keys} and store result in {@code destKey}. + * + * @param destKey must not be {@literal null}. + * @param keys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SDIFFSTORE + */ + @Nullable + Long sDiffStore(byte[] destKey, byte[]... keys); + /** * Returns the members intersecting all given sets at {@code keys}. * @@ -150,26 +171,6 @@ public interface RedisSetCommands { @Nullable Long sUnionStore(byte[] destKey, byte[]... keys); - /** - * Diff all sets for given {@code keys}. - * - * @param keys must not be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @see Redis Documentation: SDIFF - */ - @Nullable - Set sDiff(byte[]... keys); - - /** - * Diff all sets for given {@code keys} and store result in {@code destKey}. - * - * @param destKey must not be {@literal null}. - * @param keys must not be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @see Redis Documentation: SDIFFSTORE - */ - @Nullable - Long sDiffStore(byte[] destKey, byte[]... keys); /** * Get all elements of set at {@code key}. 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 9e575e8b7..1c65a9708 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java @@ -1130,18 +1130,249 @@ public interface RedisZSetCommands { Long zRemRangeByScore(byte[] key, Range range); /** - * Union sorted {@code sets} and store result in destination {@code key}. + * Diff sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + Set zDiff(byte[]... sets); + + /** + * Diff sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + Set zDiffWithScores(byte[]... sets); + + /** + * Diff sorted {@code sets} and store result in destination {@code destKey}. * * @param destKey must not be {@literal null}. * @param sets must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE + */ + @Nullable + Long zDiffStore(byte[] destKey, byte[]... sets); + + /** + * Intersect sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set zInter(byte[]... sets); + + /** + * Intersect sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + default Set zInter(Aggregate aggregate, int[] weights, byte[]... sets) { + return zInter(aggregate, Weights.of(weights), sets); + } + + /** + * Intersect sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set zInter(Aggregate aggregate, Weights weights, byte[]... sets); + + /** + * Intersect sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set zInterWithScores(byte[]... sets); + + /** + * Intersect sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + default Set zInterWithScores(Aggregate aggregate, int[] weights, byte[]... sets) { + return zInterWithScores(aggregate, Weights.of(weights), sets); + } + + /** + * Intersect sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set zInterWithScores(Aggregate aggregate, Weights weights, byte[]... sets); + + /** + * Intersect sorted {@code sets} and store result in destination {@code destKey}. + * + * @param destKey must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZINTERSTORE + */ + @Nullable + Long zInterStore(byte[] destKey, byte[]... sets); + + /** + * Intersect sorted {@code sets} and store result in destination {@code destKey}. + * + * @param destKey must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZINTERSTORE + */ + @Nullable + default Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) { + return zInterStore(destKey, aggregate, Weights.of(weights), sets); + } + + /** + * Intersect sorted {@code sets} and store result in destination {@code destKey}. + * + * @param destKey must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.1 + * @see Redis Documentation: ZINTERSTORE + */ + @Nullable + Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets); + + /** + * Union sorted {@code sets}. + * + * @param destKey must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set zUnion(byte[]... sets); + + /** + * Union sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + default Set zUnion(Aggregate aggregate, int[] weights, byte[]... sets) { + return zUnion(aggregate, Weights.of(weights), sets); + } + + /** + * Union sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set zUnion(Aggregate aggregate, Weights weights, byte[]... sets); + + /** + * Union sorted {@code sets}. + * + * @param destKey must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set zUnionWithScores(byte[]... sets); + + /** + * Union sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + default Set zUnionWithScores(Aggregate aggregate, int[] weights, byte[]... sets) { + return zUnionWithScores(aggregate, Weights.of(weights), sets); + } + + /** + * Union sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set zUnionWithScores(Aggregate aggregate, Weights weights, byte[]... sets); + + /** + * Union sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: ZUNIONSTORE */ @Nullable Long zUnionStore(byte[] destKey, byte[]... sets); /** - * Union sorted {@code sets} and store result in destination {@code key}. + * Union sorted {@code sets} and store result in destination {@code destKey}. * * @param destKey must not be {@literal null}. * @param aggregate must not be {@literal null}. @@ -1156,7 +1387,7 @@ public interface RedisZSetCommands { } /** - * Union sorted {@code sets} and store result in destination {@code key}. + * Union sorted {@code sets} and store result in destination {@code destKey}. * * @param destKey must not be {@literal null}. * @param aggregate must not be {@literal null}. @@ -1169,46 +1400,6 @@ public interface RedisZSetCommands { @Nullable Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets); - /** - * Intersect sorted {@code sets} and store result in destination {@code key}. - * - * @param destKey must not be {@literal null}. - * @param sets must not be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @see Redis Documentation: ZINTERSTORE - */ - @Nullable - Long zInterStore(byte[] destKey, byte[]... sets); - - /** - * Intersect sorted {@code sets} and store result in destination {@code key}. - * - * @param destKey must not be {@literal null}. - * @param aggregate must not be {@literal null}. - * @param weights - * @param sets must not be {@literal null}. - * @return - * @see Redis Documentation: ZINTERSTORE - */ - @Nullable - default Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) { - return zInterStore(destKey, aggregate, Weights.of(weights), sets); - } - - /** - * Intersect sorted {@code sets} and store result in destination {@code key}. - * - * @param destKey must not be {@literal null}. - * @param aggregate must not be {@literal null}. - * @param weights must not be {@literal null}. - * @param sets must not be {@literal null}. - * @return - * @since 2.1 - * @see Redis Documentation: ZINTERSTORE - */ - @Nullable - Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets); - /** * Use a {@link Cursor} to iterate over elements in sorted set at {@code key}. * 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 469b2074d..469aeed49 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -1578,28 +1578,88 @@ public interface StringRedisConnection extends RedisConnection { Long zRemRangeByScore(String key, double min, double max); /** - * Union sorted {@code sets} and store result in destination {@code key}. + * Diff sorted {@code sets}. * - * @param destKey must not be {@literal null}. * @param sets must not be {@literal null}. - * @return - * @see Redis Documentation: ZUNIONSTORE - * @see RedisZSetCommands#zUnionStore(byte[], byte[]...) + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF */ - Long zUnionStore(String destKey, String... sets); + @Nullable + Set zDiff(String... sets); /** - * Union sorted {@code sets} and store result in destination {@code key}. + * Diff sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + Set zDiffWithScores(String... sets); + + /** + * Diff sorted {@code sets} and store result in destination {@code destKey}. * * @param destKey must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE + */ + @Nullable + Long zDiffStore(String destKey, String... sets); + + /** + * Intersect sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set zInter(String... sets); + + /** + * Intersect sorted {@code sets}. + * + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set zInterWithScores(String... sets); + + /** + * Intersect sorted {@code sets}. + * * @param aggregate must not be {@literal null}. - * @param weights + * @param weights must not be {@literal null}. * @param sets must not be {@literal null}. * @return - * @see Redis Documentation: ZUNIONSTORE - * @see RedisZSetCommands#zUnionStore(byte[], Aggregate, int[], byte[]...) + * @since 2.6 + * @see Redis Documentation: ZINTER */ - Long zUnionStore(String destKey, Aggregate aggregate, int[] weights, String... sets); + @Nullable + default Set zInterWithScores(Aggregate aggregate, int[] weights, String... sets) { + return zInterWithScores(aggregate, Weights.of(weights), sets); + } + + /** + * Intersect sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set zInterWithScores(Aggregate aggregate, Weights weights, String... sets); /** * Intersect sorted {@code sets} and store result in destination {@code key}. @@ -1625,6 +1685,82 @@ public interface StringRedisConnection extends RedisConnection { */ Long zInterStore(String destKey, Aggregate aggregate, int[] weights, String... sets); + /** + * Union sorted {@code sets}. + * + * @param destKey must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set zUnion(String... sets); + + /** + * Union sorted {@code sets}. + * + * @param destKey must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set zUnionWithScores(String... sets); + + /** + * Union sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + default Set zUnionWithScores(Aggregate aggregate, int[] weights, String... sets) { + return zUnionWithScores(aggregate, Weights.of(weights), sets); + } + + /** + * Union sorted {@code sets}. + * + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set zUnionWithScores(Aggregate aggregate, Weights weights, String... sets); + + /** + * Union sorted {@code sets} and store result in destination {@code key}. + * + * @param destKey must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return + * @see Redis Documentation: ZUNIONSTORE + * @see RedisZSetCommands#zUnionStore(byte[], byte[]...) + */ + Long zUnionStore(String destKey, String... sets); + + /** + * Union sorted {@code sets} and store result in destination {@code key}. + * + * @param destKey must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @param weights + * @param sets must not be {@literal null}. + * @return + * @see Redis Documentation: ZUNIONSTORE + * @see RedisZSetCommands#zUnionStore(byte[], Aggregate, int[], byte[]...) + */ + Long zUnionStore(String destKey, Aggregate aggregate, int[] weights, String... sets); + /** * Use a {@link Cursor} to iterate over elements in sorted set at {@code key}. * 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 3334a45dc..eee02bf5a 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 @@ -64,7 +64,8 @@ class JedisClusterZSetCommands implements RedisZSetCommands { Assert.notNull(value, "Value must not be null!"); try { - return JedisConverters.toBoolean(connection.getCluster().zadd(key, score, value, JedisConverters.toZAddParams(args))); + return JedisConverters + .toBoolean(connection.getCluster().zadd(key, score, value, JedisConverters.toZAddParams(args))); } catch (Exception ex) { throw convertJedisAccessException(ex); } @@ -798,56 +799,135 @@ class JedisClusterZSetCommands implements RedisZSetCommands { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], byte[][]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiff(byte[][]) */ @Override - public Long zUnionStore(byte[] destKey, byte[]... sets) { + public Set zDiff(byte[]... sets) { - Assert.notNull(destKey, "Destination key must not be null!"); - Assert.notNull(sets, "Source sets must not be null!"); - Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.notNull(sets, "Sets must not be null!"); - byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets); - - if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { try { - return connection.getCluster().zunionstore(destKey, sets); + return connection.getCluster().zdiff(sets); } catch (Exception ex) { throw convertJedisAccessException(ex); } } - throw new InvalidDataAccessApiUsageException("ZUNIONSTORE can only be executed when all keys map to the same slot"); + throw new InvalidDataAccessApiUsageException("ZDIFF can only be executed when all keys map to the same slot"); } /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffWithScores(byte[][]) */ @Override - public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) { + public Set zDiffWithScores(byte[]... sets) { - Assert.notNull(destKey, "Destination key must not be null!"); - Assert.notNull(sets, "Source sets must not be null!"); - Assert.noNullElements(sets, "Source sets must not contain null elements!"); - Assert.isTrue(weights.size() == sets.length, () -> String - .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + Assert.notNull(sets, "Sets must not be null!"); - byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets); - - if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { - - ZParams zparams = new ZParams().weights(weights.toArray()).aggregate(ZParams.Aggregate.valueOf(aggregate.name())); + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { try { - return connection.getCluster().zunionstore(destKey, zparams, sets); + return JedisConverters.toTupleSet(connection.getCluster().zdiffWithScores(sets)); } catch (Exception ex) { throw convertJedisAccessException(ex); } } - throw new InvalidDataAccessApiUsageException("ZUNIONSTORE can only be executed when all keys map to the same slot"); + throw new InvalidDataAccessApiUsageException("ZDIFF can only be executed when all keys map to the same slot"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffStore(byte[], byte[][]) + */ + @Override + public Long zDiffStore(byte[] destKey, byte[]... sets) { + + Assert.notNull(destKey, "Destination key must not be null!"); + Assert.notNull(sets, "Source sets must not be null!"); + + byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { + + try { + return connection.getCluster().zdiffStore(destKey, sets); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZDIFFSTORE can only be executed when all keys map to the same slot"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInter(byte[][]) + */ + @Override + public Set zInter(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return connection.getCluster().zinter(new ZParams(), sets); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZINTER can only be executed when all keys map to the same slot"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterWithScores(byte[][]) + */ + @Override + public Set zInterWithScores(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return JedisConverters.toTupleSet(connection.getCluster().zinterWithScores(new ZParams(), sets)); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZINTER can only be executed when all keys map to the same slot"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Override + public Set zInterWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.isTrue(weights.size() == sets.length, () -> String + .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return JedisConverters + .toTupleSet(connection.getCluster().zinterWithScores(toZParams(aggregate, weights), sets)); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZINTER can only be executed when all keys map to the same slot"); } /* @@ -892,10 +972,8 @@ class JedisClusterZSetCommands implements RedisZSetCommands { if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { - ZParams zparams = new ZParams().weights(weights.toArray()).aggregate(ZParams.Aggregate.valueOf(aggregate.name())); - try { - return connection.getCluster().zinterstore(destKey, zparams, sets); + return connection.getCluster().zinterstore(destKey, toZParams(aggregate, weights), sets); } catch (Exception ex) { throw convertJedisAccessException(ex); } @@ -904,6 +982,127 @@ class JedisClusterZSetCommands implements RedisZSetCommands { throw new IllegalArgumentException("ZINTERSTORE can only be executed when all keys map to the same slot"); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnion(byte[][]) + */ + @Override + public Set zUnion(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return connection.getCluster().zunion(new ZParams(), sets); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZUNION can only be executed when all keys map to the same slot"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionWithScores(byte[][]) + */ + @Override + public Set zUnionWithScores(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return JedisConverters.toTupleSet(connection.getCluster().zunionWithScores(new ZParams(), sets)); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZUNION can only be executed when all keys map to the same slot"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Override + public Set zUnionWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.isTrue(weights.size() == sets.length, () -> String + .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return JedisConverters + .toTupleSet(connection.getCluster().zunionWithScores(toZParams(aggregate, weights), sets)); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZUNION can only be executed when all keys map to the same slot"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], byte[][]) + */ + @Override + public Long zUnionStore(byte[] destKey, byte[]... sets) { + + Assert.notNull(destKey, "Destination key must not be null!"); + Assert.notNull(sets, "Source sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + + byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { + + try { + return connection.getCluster().zunionstore(destKey, sets); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZUNIONSTORE can only be executed when all keys map to the same slot"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Override + public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) { + + Assert.notNull(destKey, "Destination key must not be null!"); + Assert.notNull(sets, "Source sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.isTrue(weights.size() == sets.length, () -> String + .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + + byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { + + ZParams zparams = toZParams(aggregate, weights); + + try { + return connection.getCluster().zunionstore(destKey, zparams, sets); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + throw new InvalidDataAccessApiUsageException("ZUNIONSTORE can only be executed when all keys map to the same slot"); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zScan(byte[], org.springframework.data.redis.core.ScanOptions) @@ -973,6 +1172,10 @@ class JedisClusterZSetCommands implements RedisZSetCommands { return TUPLE_SET_CONVERTER.convert(source); } + private static ZParams toZParams(Aggregate aggregate, Weights weights) { + return new ZParams().weights(weights.toArray()).aggregate(ZParams.Aggregate.valueOf(aggregate.name())); + } + /** * Workaround for broken Jedis BZPOP signature. * 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 338b5c173..7fb2a4e53 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 @@ -476,36 +476,82 @@ class JedisZSetCommands implements RedisZSetCommands { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiff(byte[][]) */ @Override - public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) { + public Set zDiff(byte[]... sets) { - Assert.notNull(destKey, "Destination key must not be null!"); - Assert.notNull(sets, "Source sets must not be null!"); - Assert.notNull(weights, "Weights must not be null!"); - Assert.noNullElements(sets, "Source sets must not contain null elements!"); - Assert.isTrue(weights.size() == sets.length, () -> String - .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + Assert.notNull(sets, "Sets must not be null!"); - ZParams zparams = new ZParams().weights(weights.toArray()).aggregate(ZParams.Aggregate.valueOf(aggregate.name())); - - return connection.invoke().just(BinaryJedis::zunionstore, MultiKeyPipelineBase::zunionstore, destKey, zparams, - sets); + return connection.invoke().just(BinaryJedis::zdiff, MultiKeyPipelineBase::zdiff, sets); } /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], byte[][]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffWithScores(byte[][]) */ @Override - public Long zUnionStore(byte[] destKey, byte[]... sets) { + public Set zDiffWithScores(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke().fromMany(BinaryJedis::zdiffWithScores, MultiKeyPipelineBase::zdiffWithScores, sets) + .toSet(JedisConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffStore(byte[], byte[][]) + */ + @Override + public Long zDiffStore(byte[] destKey, byte[]... sets) { Assert.notNull(destKey, "Destination key must not be null!"); Assert.notNull(sets, "Source sets must not be null!"); - Assert.noNullElements(sets, "Source sets must not contain null elements!"); - return connection.invoke().just(BinaryJedis::zunionstore, MultiKeyPipelineBase::zunionstore, destKey, sets); + return connection.invoke().just(BinaryJedis::zdiffStore, MultiKeyPipelineBase::zdiffStore, destKey, sets); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInter(byte[][]) + */ + @Override + public Set zInter(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke().just(BinaryJedis::zinter, MultiKeyPipelineBase::zinter, new ZParams(), sets); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterWithScores(byte[][]) + */ + @Override + public Set zInterWithScores(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke() + .fromMany(BinaryJedis::zinterWithScores, MultiKeyPipelineBase::zinterWithScores, new ZParams(), sets) + .toSet(JedisConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Override + public Set zInterWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.isTrue(weights.size() == sets.length, () -> String + .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + + return connection.invoke().fromMany(BinaryJedis::zinterWithScores, MultiKeyPipelineBase::zinterWithScores, + toZParams(aggregate, weights), sets).toSet(JedisConverters::toTuple); } /* @@ -521,7 +567,7 @@ class JedisZSetCommands implements RedisZSetCommands { Assert.isTrue(weights.size() == sets.length, () -> String .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); - ZParams zparams = new ZParams().weights(weights.toArray()).aggregate(ZParams.Aggregate.valueOf(aggregate.name())); + ZParams zparams = toZParams(aggregate, weights); return connection.invoke().just(BinaryJedis::zinterstore, MultiKeyPipelineBase::zinterstore, destKey, zparams, sets); @@ -541,6 +587,82 @@ class JedisZSetCommands implements RedisZSetCommands { return connection.invoke().just(BinaryJedis::zinterstore, MultiKeyPipelineBase::zinterstore, destKey, sets); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnion(byte[][]) + */ + @Override + public Set zUnion(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke().just(BinaryJedis::zunion, MultiKeyPipelineBase::zunion, new ZParams(), sets); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionWithScores(byte[][]) + */ + @Override + public Set zUnionWithScores(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke() + .fromMany(BinaryJedis::zunionWithScores, MultiKeyPipelineBase::zunionWithScores, new ZParams(), sets) + .toSet(JedisConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Override + public Set zUnionWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.isTrue(weights.size() == sets.length, () -> String + .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + + return connection.invoke().fromMany(BinaryJedis::zunionWithScores, MultiKeyPipelineBase::zunionWithScores, + toZParams(aggregate, weights), sets).toSet(JedisConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Override + public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) { + + Assert.notNull(destKey, "Destination key must not be null!"); + Assert.notNull(sets, "Source sets must not be null!"); + Assert.notNull(weights, "Weights must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.isTrue(weights.size() == sets.length, () -> String + .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + + ZParams zparams = toZParams(aggregate, weights); + + return connection.invoke().just(BinaryJedis::zunionstore, MultiKeyPipelineBase::zunionstore, destKey, zparams, + sets); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], byte[][]) + */ + @Override + public Long zUnionStore(byte[] destKey, byte[]... sets) { + + Assert.notNull(destKey, "Destination key must not be null!"); + Assert.notNull(sets, "Source sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + + return connection.invoke().just(BinaryJedis::zunionstore, MultiKeyPipelineBase::zunionstore, destKey, sets); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zScan(byte[], org.springframework.data.redis.core.ScanOptions) @@ -695,6 +817,10 @@ class JedisZSetCommands implements RedisZSetCommands { return connection.isQueueing(); } + private static ZParams toZParams(Aggregate aggregate, Weights weights) { + return new ZParams().weights(weights.toArray()).aggregate(ZParams.Aggregate.valueOf(aggregate.name())); + } + /** * Workaround for broken Jedis BZPOP signature. * 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 e245ccb20..2ff55abc5 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 @@ -18,6 +18,7 @@ package org.springframework.data.redis.connection.lettuce; import io.lettuce.core.ScanArgs; import io.lettuce.core.ScoredValue; import io.lettuce.core.ScoredValueScanCursor; +import io.lettuce.core.ZAggregateArgs; import io.lettuce.core.ZStoreArgs; import io.lettuce.core.api.async.RedisSortedSetAsyncCommands; import io.lettuce.core.cluster.api.sync.RedisClusterCommands; @@ -458,34 +459,83 @@ class LettuceZSetCommands implements RedisZSetCommands { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiff(byte[][]) */ @Override - public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) { + public Set zDiff(byte[]... sets) { - Assert.notNull(destKey, "Destination key must not be null!"); - Assert.notNull(sets, "Source sets must not be null!"); - Assert.noNullElements(sets, "Source sets must not contain null elements!"); - Assert.isTrue(weights.size() == sets.length, () -> String - .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + Assert.notNull(sets, "Sets must not be null!"); - ZStoreArgs storeArgs = zStoreArgs(aggregate, weights); - - return connection.invoke().just(RedisSortedSetAsyncCommands::zunionstore, destKey, storeArgs, sets); + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zdiff, sets).toSet(); } /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], byte[][]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffWithScores(byte[][]) */ @Override - public Long zUnionStore(byte[] destKey, byte[]... sets) { + public Set zDiffWithScores(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zdiffWithScores, sets) + .toSet(LettuceConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffStore(byte[], byte[][]) + */ + @Override + public Long zDiffStore(byte[] destKey, byte[]... sets) { Assert.notNull(destKey, "Destination key must not be null!"); Assert.notNull(sets, "Source sets must not be null!"); - Assert.noNullElements(sets, "Source sets must not contain null elements!"); - return connection.invoke().just(RedisSortedSetAsyncCommands::zunionstore, destKey, sets); + return connection.invoke().just(RedisSortedSetAsyncCommands::zdiffstore, destKey, sets); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInter(byte[][]) + */ + @Override + public Set zInter(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zinter, sets).toSet(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterWithScores(byte[][]) + */ + @Override + public Set zInterWithScores(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zinterWithScores, sets) + .toSet(LettuceConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Override + public Set zInterWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.isTrue(weights.size() == sets.length, () -> String + .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + + ZAggregateArgs zAggregateArgs = zAggregateArgs(aggregate, weights); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zinterWithScores, zAggregateArgs, sets) + .toSet(LettuceConverters::toTuple); } /* @@ -520,6 +570,81 @@ class LettuceZSetCommands implements RedisZSetCommands { return connection.invoke().just(RedisSortedSetAsyncCommands::zinterstore, destKey, sets); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnion(byte[][]) + */ + @Override + public Set zUnion(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zunion, sets).toSet(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionWithScores(byte[][]) + */ + @Override + public Set zUnionWithScores(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zunionWithScores, sets) + .toSet(LettuceConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionWithScores(org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Override + public Set zUnionWithScores(Aggregate aggregate, Weights weights, byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.isTrue(weights.size() == sets.length, () -> String + .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + + ZAggregateArgs zAggregateArgs = zAggregateArgs(aggregate, weights); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zunionWithScores, zAggregateArgs, sets) + .toSet(LettuceConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + */ + @Override + public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) { + + Assert.notNull(destKey, "Destination key must not be null!"); + Assert.notNull(sets, "Source sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + Assert.isTrue(weights.size() == sets.length, () -> String + .format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length)); + + ZStoreArgs storeArgs = zStoreArgs(aggregate, weights); + + return connection.invoke().just(RedisSortedSetAsyncCommands::zunionstore, destKey, storeArgs, sets); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], byte[][]) + */ + @Override + public Long zUnionStore(byte[] destKey, byte[]... sets) { + + Assert.notNull(destKey, "Destination key must not be null!"); + Assert.notNull(sets, "Source sets must not be null!"); + Assert.noNullElements(sets, "Source sets must not contain null elements!"); + + return connection.invoke().just(RedisSortedSetAsyncCommands::zunionstore, destKey, sets); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zScan(byte[], org.springframework.data.redis.core.ScanOptions) @@ -684,6 +809,29 @@ class LettuceZSetCommands implements RedisZSetCommands { return args; } + private static ZAggregateArgs zAggregateArgs(@Nullable Aggregate aggregate, Weights weights) { + + ZAggregateArgs args = new ZAggregateArgs(); + + if (aggregate != null) { + switch (aggregate) { + case MIN: + args.min(); + break; + case MAX: + args.max(); + break; + default: + args.sum(); + break; + } + } + + args.weights(weights.toArray()); + + return args; + } + /** * Convert {@link ZAddArgs} to {@link io.lettuce.core.ZAddArgs}. * diff --git a/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java b/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java index 82c694af5..7aa2e6a5b 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.core; import java.time.Duration; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -53,7 +54,7 @@ public interface BoundZSetOperations extends BoundKeyOperations { Boolean add(V value, double score); /** - * Add {@code value} to a sorted set at {@code key} if it does not already exists. + * Add {@code value} to a sorted set at the bound key if it does not already exists. * * @param score the score. * @param value the value. @@ -75,7 +76,7 @@ public interface BoundZSetOperations extends BoundKeyOperations { Long add(Set> tuples); /** - * Add {@code tuples} to a sorted set at {@code key} if it does not already exists. + * Add {@code tuples} to a sorted set at the bound key if it does not already exists. * * @param tuples must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. @@ -418,41 +419,6 @@ public interface BoundZSetOperations extends BoundKeyOperations { @Nullable Long removeRangeByScore(double min, double max); - /** - * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. - * - * @param otherKey must not be {@literal null}. - * @param destKey must not be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @see Redis Documentation: ZUNIONSTORE - */ - @Nullable - Long unionAndStore(K otherKey, K destKey); - - /** - * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. - * - * @param otherKeys must not be {@literal null}. - * @param destKey must not be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @see Redis Documentation: ZUNIONSTORE - */ - @Nullable - Long unionAndStore(Collection otherKeys, K destKey); - - /** - * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. - * - * @param otherKeys must not be {@literal null}. - * @param destKey must not be {@literal null}. - * @param aggregate must not be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @since 2.1 - * @see Redis Documentation: ZUNIONSTORE - */ - @Nullable - Long unionAndStore(Collection otherKeys, K destKey, Aggregate aggregate); - /** * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. * @@ -467,6 +433,127 @@ public interface BoundZSetOperations extends BoundKeyOperations { @Nullable Long unionAndStore(Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); + /** + * Diff sorted {@code sets}. + * + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + default Set difference(K otherKey) { + return difference(Collections.singleton(otherKey)); + } + + /** + * Diff sorted {@code sets}. + * + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + Set difference(Collection otherKeys); + + /** + * Diff sorted {@code sets}. + * + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + default Set> differenceWithScores(K otherKey) { + return differenceWithScores(Collections.singleton(otherKey)); + } + + /** + * Diff sorted {@code sets}. + * + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + Set> differenceWithScores(Collection otherKeys); + + /** + * Diff sorted {@code sets} and store result in destination {@code destKey}. + * + * @param otherKeys must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE + */ + @Nullable + Long differenceAndStore(Collection otherKeys, K destKey); + + /** + * Intersect sorted {@code sets}. + * + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + default Set intersect(K otherKey) { + return intersect(Collections.singleton(otherKey)); + } + + /** + * Intersect sorted {@code sets}. + * + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set intersect(Collection otherKeys); + + /** + * Intersect sorted {@code sets}. + * + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + default Set> intersectWithScores(K otherKey) { + return intersectWithScores(Collections.singleton(otherKey)); + } + + /** + * Intersect sorted {@code sets}. + * + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set> intersectWithScores(Collection otherKeys); + + /** + * Intersect sorted {@code sets}. + * + * @param otherKeys must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set> intersectWithScores(Collection otherKeys, Aggregate aggregate, Weights weights); + /** * Intersect sorted sets at the bound key and {@code otherKey} and store result in destination {@code destKey}. * @@ -516,6 +603,116 @@ public interface BoundZSetOperations extends BoundKeyOperations { @Nullable Long intersectAndStore(Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); + /** + * Union sorted {@code sets}. + * + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + default Set union(K otherKey) { + return union(Collections.singleton(otherKey)); + } + + /** + * Union sorted {@code sets}. + * + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set union(Collection otherKeys); + + /** + * Union sorted {@code sets}. + * + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + default Set> unionWithScores(K otherKey) { + return unionWithScores(Collections.singleton(otherKey)); + } + + /** + * Union sorted {@code sets}. + * + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set> unionWithScores(Collection otherKeys); + + /** + * Union sorted sets at the bound key and {@code otherKeys}. + * + * @param otherKeys must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + default Set> unionWithScores(Collection otherKeys, Aggregate aggregate) { + return unionWithScores(otherKeys, aggregate, Weights.fromSetCount(1 + otherKeys.size())); + } + + /** + * Union sorted {@code sets}. + * + * @param otherKeys must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set> unionWithScores(Collection otherKeys, Aggregate aggregate, Weights weights); + + /** + * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. + * + * @param otherKey must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZUNIONSTORE + */ + @Nullable + Long unionAndStore(K otherKey, K destKey); + + /** + * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. + * + * @param otherKeys must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZUNIONSTORE + */ + @Nullable + Long unionAndStore(Collection otherKeys, K destKey); + + /** + * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. + * + * @param otherKeys must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.1 + * @see Redis Documentation: ZUNIONSTORE + */ + @Nullable + Long unionAndStore(Collection otherKeys, K destKey, Aggregate aggregate); + /** * Iterate over elements in zset at the bound key.
* Important: Call {@link Cursor#close()} when done to avoid resource leak. diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java index 848c4318f..a12c1287a 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java @@ -108,42 +108,6 @@ class DefaultBoundZSetOperations extends DefaultBoundKeyOperations impl return ops.getOperations(); } - /* - * (non-Javadoc) - * @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.lang.Object, java.lang.Object) - */ - @Override - public Long intersectAndStore(K otherKey, K destKey) { - return ops.intersectAndStore(getKey(), otherKey, destKey); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.util.Collection, java.lang.Object) - */ - @Override - public Long intersectAndStore(Collection otherKeys, K destKey) { - return ops.intersectAndStore(getKey(), otherKeys, destKey); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate) - */ - @Override - public Long intersectAndStore(Collection otherKeys, K destKey, Aggregate aggregate) { - return ops.intersectAndStore(getKey(), otherKeys, destKey, aggregate); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) - */ - @Override - public Long intersectAndStore(Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { - return ops.intersectAndStore(getKey(), otherKeys, destKey, aggregate, weights); - } - /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundZSetOperations#range(long, long) @@ -402,6 +366,132 @@ class DefaultBoundZSetOperations extends DefaultBoundKeyOperations impl return ops.zCard(getKey()); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#difference(java.util.Collection) + */ + @Nullable + @Override + public Set difference(Collection otherKeys) { + return ops.difference(getKey(), otherKeys); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#differenceWithScores(java.util.Collection) + */ + @Nullable + @Override + public Set> differenceWithScores(Collection otherKeys) { + return ops.differenceWithScores(getKey(), otherKeys); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#differenceAndStore(java.util.Collection, java.lang.Object) + */ + @Nullable + @Override + public Long differenceAndStore(Collection otherKeys, K destKey) { + return ops.differenceAndStore(getKey(), otherKeys, destKey); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#intersect(java.util.Collection) + */ + @Nullable + @Override + public Set intersect(Collection otherKeys) { + return ops.intersect(getKey(), otherKeys); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#intersectWithScores(java.util.Collection) + */ + @Nullable + @Override + public Set> intersectWithScores(Collection otherKeys) { + return ops.intersectWithScores(getKey(), otherKeys); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#intersectWithScores(java.util.Collection, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) + */ + @Nullable + @Override + public Set> intersectWithScores(Collection otherKeys, Aggregate aggregate, Weights weights) { + return ops.intersectWithScores(getKey(), otherKeys, aggregate, weights); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.lang.Object, java.lang.Object) + */ + @Override + public Long intersectAndStore(K otherKey, K destKey) { + return ops.intersectAndStore(getKey(), otherKey, destKey); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.util.Collection, java.lang.Object) + */ + @Override + public Long intersectAndStore(Collection otherKeys, K destKey) { + return ops.intersectAndStore(getKey(), otherKeys, destKey); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate) + */ + @Override + public Long intersectAndStore(Collection otherKeys, K destKey, Aggregate aggregate) { + return ops.intersectAndStore(getKey(), otherKeys, destKey, aggregate); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) + */ + @Override + public Long intersectAndStore(Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { + return ops.intersectAndStore(getKey(), otherKeys, destKey, aggregate, weights); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#union(java.util.Collection) + */ + @Nullable + @Override + public Set union(Collection otherKeys) { + return ops.union(getKey(), otherKeys); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#unionWithScores(java.util.Collection) + */ + @Nullable + @Override + public Set> unionWithScores(Collection otherKeys) { + return ops.unionWithScores(getKey(), otherKeys); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#unionWithScores(java.util.Collection, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) + */ + @Nullable + @Override + public Set> unionWithScores(Collection otherKeys, Aggregate aggregate, Weights weights) { + return ops.unionWithScores(getKey(), otherKeys, aggregate, weights); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundZSetOperations#unionAndStore(java.lang.Object, java.lang.Object) 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 c905088b6..a1e965d9d 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java @@ -130,41 +130,6 @@ class DefaultZSetOperations extends AbstractOperations implements ZS return execute(connection -> connection.zIncrBy(rawKey, delta, rawValue), true); } - /* - * (non-Javadoc) - * @see org.springframework.data.redis.core.ZSetOperations#intersectAndStore(java.lang.Object, java.lang.Object, java.lang.Object) - */ - @Override - public Long intersectAndStore(K key, K otherKey, K destKey) { - return intersectAndStore(key, Collections.singleton(otherKey), destKey); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.core.ZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object) - */ - @Override - public Long intersectAndStore(K key, Collection otherKeys, K destKey) { - - byte[][] rawKeys = rawKeys(key, otherKeys); - byte[] rawDestKey = rawKey(destKey); - - return execute(connection -> connection.zInterStore(rawDestKey, rawKeys), true); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.core.ZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) - */ - @Override - public Long intersectAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { - - byte[][] rawKeys = rawKeys(key, otherKeys); - byte[] rawDestKey = rawKey(destKey); - - return execute(connection -> connection.zInterStore(rawDestKey, aggregate, weights, rawKeys), true); - } - /* * (non-Javadoc) * @see org.springframework.data.redis.core.ZSetOperations#range(java.lang.Object, long, long) @@ -565,6 +530,147 @@ class DefaultZSetOperations extends AbstractOperations implements ZS return execute(connection -> connection.zCard(rawKey), true); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#difference(java.lang.Object, java.util.Collection) + */ + @Override + public Set difference(K key, Collection otherKeys) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + Set rawValues = execute(connection -> connection.zDiff(rawKeys), true); + return deserializeValues(rawValues); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#differenceWithScores(java.lang.Object, java.util.Collection) + */ + @Override + public Set> differenceWithScores(K key, Collection otherKeys) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + return deserializeTupleValues(execute(connection -> connection.zDiffWithScores(rawKeys), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#differenceAndStore(java.lang.Object, java.util.Collection, java.lang.Object) + */ + @Override + public Long differenceAndStore(K key, Collection otherKeys, K destKey) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + byte[] rawDestKey = rawKey(destKey); + + return execute(connection -> connection.zDiffStore(rawDestKey, rawKeys), true); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#intersect(java.lang.Object, java.util.Collection) + */ + @Override + public Set intersect(K key, Collection otherKeys) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + Set rawValues = execute(connection -> connection.zInter(rawKeys), true); + return deserializeValues(rawValues); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#intersectWithScores(java.lang.Object, java.util.Collection) + */ + @Override + public Set> intersectWithScores(K key, Collection otherKeys) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + return deserializeTupleValues(execute(connection -> connection.zInterWithScores(rawKeys), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#intersectWithScores(java.lang.Object, java.util.Collection, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) + */ + @Override + public Set> intersectWithScores(K key, Collection otherKeys, Aggregate aggregate, Weights weights) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + return deserializeTupleValues( + execute(connection -> connection.zInterWithScores(aggregate, weights, rawKeys), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#intersectAndStore(java.lang.Object, java.lang.Object, java.lang.Object) + */ + @Override + public Long intersectAndStore(K key, K otherKey, K destKey) { + return intersectAndStore(key, Collections.singleton(otherKey), destKey); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object) + */ + @Override + public Long intersectAndStore(K key, Collection otherKeys, K destKey) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + byte[] rawDestKey = rawKey(destKey); + + return execute(connection -> connection.zInterStore(rawDestKey, rawKeys), true); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) + */ + @Override + public Long intersectAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + byte[] rawDestKey = rawKey(destKey); + + return execute(connection -> connection.zInterStore(rawDestKey, aggregate, weights, rawKeys), true); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#union(java.lang.Object, java.util.Collection) + */ + @Override + public Set union(K key, Collection otherKeys) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + Set rawValues = execute(connection -> connection.zUnion(rawKeys), true); + return deserializeValues(rawValues); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#unionWithScores(java.lang.Object, java.util.Collection) + */ + @Override + public Set> unionWithScores(K key, Collection otherKeys) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + return deserializeTupleValues(execute(connection -> connection.zUnionWithScores(rawKeys), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#unionWithScores(java.lang.Object, java.util.Collection, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) + */ + @Override + public Set> unionWithScores(K key, Collection otherKeys, Aggregate aggregate, Weights weights) { + + byte[][] rawKeys = rawKeys(key, otherKeys); + return deserializeTupleValues( + execute(connection -> connection.zUnionWithScores(aggregate, weights, rawKeys), true)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ZSetOperations#unionAndStore(java.lang.Object, java.lang.Object, java.lang.Object) 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 b862c45f6..11823e197 100644 --- a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.core; import java.time.Duration; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -537,59 +538,150 @@ public interface ZSetOperations { Long removeRangeByScore(K key, double min, double max); /** - * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. + * Diff sorted {@code sets}. * * @param key must not be {@literal null}. * @param otherKey must not be {@literal null}. - * @param destKey must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. - * @see Redis Documentation: ZUNIONSTORE + * @since 2.6 + * @see Redis Documentation: ZDIFF */ @Nullable - Long unionAndStore(K key, K otherKey, K destKey); - - /** - * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. - * - * @param key must not be {@literal null}. - * @param otherKeys must not be {@literal null}. - * @param destKey must not be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @see Redis Documentation: ZUNIONSTORE - */ - @Nullable - Long unionAndStore(K key, Collection otherKeys, K destKey); - - /** - * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. - * - * @param key must not be {@literal null}. - * @param otherKeys must not be {@literal null}. - * @param destKey must not be {@literal null}. - * @param aggregate must not be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @since 2.1 - * @see Redis Documentation: ZUNIONSTORE - */ - @Nullable - default Long unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate) { - return unionAndStore(key, otherKeys, destKey, aggregate, Weights.fromSetCount(1 + otherKeys.size())); + default Set difference(K key, K otherKey) { + return difference(key, Collections.singleton(otherKey)); } /** - * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. + * Diff sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + Set difference(K key, Collection otherKeys); + + /** + * Diff sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + default Set> differenceWithScores(K key, K otherKey) { + return differenceWithScores(key, Collections.singleton(otherKey)); + } + + /** + * Diff sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + @Nullable + Set> differenceWithScores(K key, Collection otherKeys); + + /** + * Diff sorted {@code sets} and store result in destination {@code destKey}. * * @param key must not be {@literal null}. * @param otherKeys must not be {@literal null}. * @param destKey must not be {@literal null}. - * @param aggregate must not be {@literal null}. - * @param weights must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. - * @since 2.1 - * @see Redis Documentation: ZUNIONSTORE + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE */ @Nullable - Long unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); + Long differenceAndStore(K key, Collection otherKeys, K destKey); + + /** + * Intersect sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + default Set intersect(K key, K otherKey) { + return intersect(key, Collections.singleton(otherKey)); + } + + /** + * Intersect sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set intersect(K key, Collection otherKeys); + + /** + * Intersect sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + default Set> intersectWithScores(K key, K otherKey) { + return intersectWithScores(key, Collections.singleton(otherKey)); + } + + /** + * Intersect sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set> intersectWithScores(K key, Collection otherKeys); + + /** + * Intersect sorted sets at {@code key} and {@code otherKeys} . + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + default Set> intersectWithScores(K key, Collection otherKeys, Aggregate aggregate) { + return intersectWithScores(key, otherKeys, aggregate, Weights.fromSetCount(1 + otherKeys.size())); + } + + /** + * Intersect sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + @Nullable + Set> intersectWithScores(K key, Collection otherKeys, Aggregate aggregate, Weights weights); /** * Intersect sorted sets at {@code key} and {@code otherKey} and store result in destination {@code destKey}. @@ -646,6 +738,142 @@ public interface ZSetOperations { @Nullable Long intersectAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); + /** + * Union sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + default Set union(K key, K otherKey) { + return union(key, Collections.singleton(otherKey)); + } + + /** + * Union sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set union(K key, Collection otherKeys); + + /** + * Union sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + default Set> unionWithScores(K key, K otherKey) { + return unionWithScores(key, Collections.singleton(otherKey)); + } + + /** + * Union sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set> unionWithScores(K key, Collection otherKeys); + + /** + * Union sorted sets at {@code key} and {@code otherKeys} . + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + default Set> unionWithScores(K key, Collection otherKeys, Aggregate aggregate) { + return unionWithScores(key, otherKeys, aggregate, Weights.fromSetCount(1 + otherKeys.size())); + } + + /** + * Union sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + @Nullable + Set> unionWithScores(K key, Collection otherKeys, Aggregate aggregate, Weights weights); + + /** + * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZUNIONSTORE + */ + @Nullable + Long unionAndStore(K key, K otherKey, K destKey); + + /** + * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZUNIONSTORE + */ + @Nullable + Long unionAndStore(K key, Collection otherKeys, K destKey); + + /** + * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.1 + * @see Redis Documentation: ZUNIONSTORE + */ + @Nullable + default Long unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate) { + return unionAndStore(key, otherKeys, destKey, aggregate, Weights.fromSetCount(1 + otherKeys.size())); + } + + /** + * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. + * + * @param key must not be {@literal null}. + * @param otherKeys must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @param aggregate must not be {@literal null}. + * @param weights must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.1 + * @see Redis Documentation: ZUNIONSTORE + */ + @Nullable + Long unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); + /** * Iterate over elements in zset at {@code key}.
* Important: Call {@link Cursor#close()} when done to avoid resource leak. @@ -688,38 +916,38 @@ public interface ZSetOperations { @Nullable Set rangeByLex(K key, Range range, Limit limit); - /** - * Get all elements with reverse lexicographical ordering from {@literal ZSET} at {@code key} with a value between - * {@link Range#getMin()} and {@link Range#getMax()}. - * - * @param key must not be {@literal null}. - * @param range must not be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @since 2.4 - * @see Redis Documentation: ZREVRANGEBYLEX - */ - @Nullable - default Set reverseRangeByLex(K key, Range range) { - return reverseRangeByLex(key, range, Limit.unlimited()); + /** + * Get all elements with reverse lexicographical ordering from {@literal ZSET} at {@code key} with a value between + * {@link Range#getMin()} and {@link Range#getMax()}. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.4 + * @see Redis Documentation: ZREVRANGEBYLEX + */ + @Nullable + default Set reverseRangeByLex(K key, Range range) { + return reverseRangeByLex(key, range, Limit.unlimited()); + } + + /** + * Get all elements {@literal n} elements, where {@literal n = } {@link Limit#getCount()}, starting at + * {@link Limit#getOffset()} with reverse lexicographical ordering from {@literal ZSET} at {@code key} with a value + * between {@link Range#getMin()} and {@link Range#getMax()}. + * + * @param key must not be {@literal null} + * @param range must not be {@literal null}. + * @param limit can be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.4 + * @see Redis Documentation: ZREVRANGEBYLEX + */ + @Nullable + Set reverseRangeByLex(K key, Range range, Limit limit); + + /** + * @return never {@literal null}. + */ + RedisOperations getOperations(); } - - /** - * Get all elements {@literal n} elements, where {@literal n = } {@link Limit#getCount()}, starting at - * {@link Limit#getOffset()} with reverse lexicographical ordering from {@literal ZSET} at {@code key} with a value - * between {@link Range#getMin()} and {@link Range#getMax()}. - * - * @param key must not be {@literal null} - * @param range must not be {@literal null}. - * @param limit can be {@literal null}. - * @return {@literal null} when used in pipeline / transaction. - * @since 2.4 - * @see Redis Documentation: ZREVRANGEBYLEX - */ - @Nullable - Set reverseRangeByLex(K key, Range range, Limit limit); - - /** - * @return never {@literal null}. - */ - RedisOperations getOperations(); -} 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 e45e3c33e..881494630 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -1887,6 +1887,64 @@ public abstract class AbstractConnectionIntegrationTests { Arrays.asList(new Object[] { true, true, true, 6d, new LinkedHashSet<>(Collections.singletonList("Joe")) })); } + @Test // GH-2041 + @EnabledOnCommand("ZDIFF") + void testZDiff() { + 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.zDiff("myset", "otherset")); + actual.add(connection.zDiffWithScores("myset", "otherset")); + verifyResults(Arrays.asList(new Object[] { true, true, true, true, true, Collections.singleton("Joe"), + Collections.singleton(new DefaultStringTuple("Joe", 4)) })); + } + + @Test // GH-2041 + @EnabledOnCommand("ZDIFFSTORE") + void testZDiffStore() { + 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.zDiffStore("thirdset", "myset", "otherset")); + actual.add(connection.zRange("thirdset", 0, -1)); + verifyResults(Arrays.asList(new Object[] { true, true, true, true, true, 1L, Collections.singleton("Joe") })); + } + + @Test // GH-2042 + @EnabledOnCommand("ZINTER") + void testZInter() { + 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.zInter("myset", "otherset")); + actual.add(connection.zInterWithScores("myset", "otherset")); + verifyResults(Arrays.asList(new Object[] { true, true, true, true, true, + new LinkedHashSet<>(Arrays.asList("Bob", "James")), + new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("Bob", 3d), new DefaultStringTuple("James", 5))) })); + } + + @Test // GH-2042 + @EnabledOnCommand("ZINTER") + void testZInterAggWeights() { + 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.zInter("myset", "otherset")); + actual.add(connection.zInterWithScores(Aggregate.MAX, new int[] { 2, 3 }, "myset", "otherset")); + + verifyResults(Arrays.asList(new Object[] { true, true, true, true, true, + new LinkedHashSet<>(Arrays.asList("Bob", "James")), + new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("Bob", 4d), new DefaultStringTuple("James", 12d))) })); + } + @Test void testZInterStore() { actual.add(connection.zAdd("myset", 2, "Bob")); @@ -2114,6 +2172,39 @@ public abstract class AbstractConnectionIntegrationTests { verifyResults(Arrays.asList(new Object[] { true, true, true, Arrays.asList(1d, 3d, null) })); } + @Test // GH-2042 + @EnabledOnCommand("ZUNION") + void testZUnion() { + 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.zUnion("myset", "otherset")); + actual.add(connection.zUnionWithScores("myset", "otherset")); + verifyResults(Arrays + .asList(new Object[] { true, true, true, true, true, new LinkedHashSet<>(Arrays.asList("Bob", "James", "Joe")), + new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("Bob", 3d), new DefaultStringTuple("James", 5), + new DefaultStringTuple("Joe", 4))) })); + } + + @Test // GH-2042 + @EnabledOnCommand("ZUNION") + void testZUnionAggWeights() { + 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.zUnion("myset", "otherset")); + actual.add(connection.zUnionWithScores(Aggregate.MAX, new int[] { 2, 3 }, "myset", "otherset")); + + verifyResults(Arrays + .asList(new Object[] { true, true, true, true, true, new LinkedHashSet<>(Arrays.asList("Bob", "James", "Joe")), + new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("Bob", 4d), new DefaultStringTuple("Joe", 8d), + new DefaultStringTuple("James", 12d))) })); + } + @Test void testZUnionStore() { @@ -2139,9 +2230,8 @@ public abstract class AbstractConnectionIntegrationTests { actual.add(connection.zUnionStore("thirdset", Aggregate.MAX, new int[] { 2, 3 }, "myset", "otherset")); actual.add(connection.zRangeWithScores("thirdset", 0, -1)); verifyResults(Arrays.asList(new Object[] { true, true, true, true, true, 3L, - new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("Bob".getBytes(), "Bob", 4d), - new DefaultStringTuple("Joe".getBytes(), "Joe", 8d), - new DefaultStringTuple("James".getBytes(), "James", 12d))) })); + new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("Bob", 4d), new DefaultStringTuple("Joe", 8d), + new DefaultStringTuple("James", 12d))) })); } // Hash Ops 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 661fc4fd4..f8c1d7b1f 100644 --- a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java @@ -604,6 +604,21 @@ public interface ClusterConnectionTests { // DATAREDIS-315 void zIncrByShouldIncScoreForValueCorrectly(); + // GH-2041 + void zDiffShouldThrowExceptionWhenKeysDoNotMapToSameSlots(); + + // GH-2041 + void zDiffShouldWorkForSameSlotKeys(); + + // GH-2041 + void zDiffStoreShouldWorkForSameSlotKeys(); + + // GH-2042 + void zInterShouldThrowExceptionWhenKeysDoNotMapToSameSlots(); + + // GH-2042 + void zInterShouldWorkForSameSlotKeys(); + // DATAREDIS-315 void zInterStoreShouldThrowExceptionWhenKeysDoNotMapToSameSlots(); @@ -685,6 +700,12 @@ public interface ClusterConnectionTests { // GH-2038 void zMScoreShouldRetrieveScoreForValues(); + // GH-2042 + void zUnionShouldThrowExceptionWhenKeysDoNotMapToSameSlots(); + + // GH-2042 + void zUnionShouldWorkForSameSlotKeys(); + // DATAREDIS-315 void zUnionStoreShouldThrowExceptionWhenKeysDoNotMapToSameSlots(); 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 b14727033..20fbe77cc 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 @@ -2006,7 +2006,67 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests { assertThat(nativeConnection.zrank(KEY_1_BYTES, VALUE_1_BYTES)).isEqualTo(1L); } - @Test // DATAREDIS-315 + @Test // GH-2041 + public void zDiffShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zDiff(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zDiffStore(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zDiffWithScores(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + } + + @Test // GH-2041 + @EnabledOnCommand("ZDIFF") + public void zDiffShouldWorkForSameSlotKeys() { + + 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); + + assertThat(clusterConnection.zDiff(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).contains(VALUE_1_BYTES); + assertThat(clusterConnection.zDiffWithScores(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)) + .contains(new DefaultTuple(VALUE_1_BYTES, 10D)); + } + + @Test // GH-2041 + @EnabledOnCommand("ZDIFFSTORE") + public void zDiffStoreShouldWorkForSameSlotKeys() { + + 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); + + clusterConnection.zDiffStore(SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.zrange(SAME_SLOT_KEY_3_BYTES, 0, -1)).contains(VALUE_1_BYTES); + } + + @Test // GH-2042 + public void zInterShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zInter(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zInterWithScores(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + } + + @Test // GH-2042 + @EnabledOnCommand("ZINTER") + public void zInterShouldWorkForSameSlotKeys() { + + 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); + + assertThat(clusterConnection.zInter(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).contains(VALUE_2_BYTES); + assertThat(clusterConnection.zInterWithScores(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)) + .contains(new DefaultTuple(VALUE_2_BYTES, 40D)); + } + + @Test // GH-2042 public void zInterStoreShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { assertThatExceptionOfType(DataAccessException.class) .isThrownBy(() -> clusterConnection.zInterStore(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); @@ -2364,6 +2424,29 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests { assertThat(clusterConnection.zMScore(KEY_1_BYTES, VALUE_1_BYTES, VALUE_2_BYTES)).containsSequence(10D, 20D); } + @Test // GH-2042 + public void zUnionShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zUnion(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zUnionWithScores(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + } + + @Test // GH-2042 + @EnabledOnCommand("ZUNION") + public void zUnionShouldWorkForSameSlotKeys() { + + nativeConnection.zadd(SAME_SLOT_KEY_1_BYTES, 10D, VALUE_1_BYTES); + nativeConnection.zadd(SAME_SLOT_KEY_1_BYTES, 30D, VALUE_3_BYTES); + nativeConnection.zadd(SAME_SLOT_KEY_2_BYTES, 20D, VALUE_2_BYTES); + + assertThat(clusterConnection.zUnion(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).contains(VALUE_1_BYTES, + VALUE_2_BYTES, VALUE_3_BYTES); + assertThat(clusterConnection.zUnionWithScores(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).contains( + new DefaultTuple(VALUE_1_BYTES, 10D), new DefaultTuple(VALUE_2_BYTES, 20D), + new DefaultTuple(VALUE_3_BYTES, 30D)); + } + @Test // DATAREDIS-315 public void zUnionStoreShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { assertThatExceptionOfType(DataAccessException.class) 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 d6c098fea..d87d18188 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 @@ -2047,6 +2047,66 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests { assertThat(nativeConnection.zrank(KEY_1, VALUE_1)).isEqualTo(1L); } + @Test // GH-2041 + public void zDiffShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zDiff(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zDiffStore(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zDiffWithScores(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + } + + @Test // GH-2041 + @EnabledOnCommand("ZDIFF") + public void zDiffShouldWorkForSameSlotKeys() { + + 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); + + assertThat(clusterConnection.zDiff(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).contains(VALUE_1_BYTES); + assertThat(clusterConnection.zDiffWithScores(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)) + .contains(new DefaultTuple(VALUE_1_BYTES, 10D)); + } + + @Test // GH-2041 + @EnabledOnCommand("ZDIFFSTORE") + public void zDiffStoreShouldWorkForSameSlotKeys() { + + 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); + + clusterConnection.zDiffStore(SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.zrange(SAME_SLOT_KEY_3, 0, -1)).contains(VALUE_1); + } + + @Test // GH-2042 + public void zInterShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zInter(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zInterWithScores(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + } + + @Test // GH-2042 + @EnabledOnCommand("ZINTER") + public void zInterShouldWorkForSameSlotKeys() { + + 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); + + assertThat(clusterConnection.zInter(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).contains(VALUE_2_BYTES); + assertThat(clusterConnection.zInterWithScores(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)) + .contains(new DefaultTuple(VALUE_2_BYTES, 40D)); + } + @Test // DATAREDIS-315 public void zInterStoreShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { assertThatExceptionOfType(DataAccessException.class) @@ -2405,6 +2465,29 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests { assertThat(clusterConnection.zMScore(KEY_1_BYTES, VALUE_1_BYTES, VALUE_2_BYTES)).containsExactly(10D, 20D); } + @Test // GH-2042 + public void zUnionShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zUnion(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> clusterConnection.zUnionWithScores(KEY_3_BYTES, KEY_1_BYTES, KEY_2_BYTES)); + } + + @Test // GH-2042 + @EnabledOnCommand("ZUNION") + public void zUnionShouldWorkForSameSlotKeys() { + + nativeConnection.zadd(SAME_SLOT_KEY_1, 10D, VALUE_1); + nativeConnection.zadd(SAME_SLOT_KEY_1, 30D, VALUE_3); + nativeConnection.zadd(SAME_SLOT_KEY_2, 20D, VALUE_2); + + assertThat(clusterConnection.zUnion(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).contains(VALUE_1_BYTES, + VALUE_2_BYTES, VALUE_3_BYTES); + assertThat(clusterConnection.zUnionWithScores(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES)).contains( + new DefaultTuple(VALUE_1_BYTES, 10D), new DefaultTuple(VALUE_2_BYTES, 20D), + new DefaultTuple(VALUE_3_BYTES, 30D)); + } + @Test // DATAREDIS-315 public void zUnionStoreShouldThrowExceptionWhenKeysDoNotMapToSameSlots() { assertThatExceptionOfType(DataAccessException.class) 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 62f33ef7b..600337cb4 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java @@ -462,7 +462,117 @@ public class DefaultZSetOperationsIntegrationTests { assertThat(count).isEqualTo(3); } + @ParameterizedRedisTest // GH-2042 + @EnabledOnCommand("ZDIFF") + void testZsetDiff() { + + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + zSetOps.add(key1, value1, 1.0); + zSetOps.add(key1, value2, 2.0); + zSetOps.add(key2, value2, 3.0); + + assertThat(zSetOps.difference(key1, key2)).containsOnly(value1); + assertThat(zSetOps.differenceWithScores(key1, key2)).containsOnly(new DefaultTypedTuple<>(value1, 1d)); + } + + @ParameterizedRedisTest // DATAREDIS-746, GH-2042 + @EnabledOnCommand("ZDIFFSTORE") + void testZsetDiffStore() { + + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + K key3 = keyFactory.instance(); + + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + zSetOps.add(key1, value1, 1.0); + zSetOps.add(key1, value2, 2.0); + zSetOps.add(key2, value2, 3.0); + + assertThat(zSetOps.differenceAndStore(key1, Collections.singletonList(key2), key3)).isEqualTo(1); + + assertThat(zSetOps.rangeWithScores(key3, 0, -1)).containsOnly(new DefaultTypedTuple<>(value1, 1d)); + } + + @ParameterizedRedisTest // GH-2042 + @EnabledOnCommand("ZINTER") + void testZsetIntersect() { + + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + zSetOps.add(key1, value1, 1.0); + zSetOps.add(key1, value2, 2.0); + zSetOps.add(key2, value2, 3.0); + + assertThat(zSetOps.intersect(key1, Collections.singletonList(key2))).containsExactly(value2); + } + + @ParameterizedRedisTest // DATAREDIS-746, GH-2042 + @EnabledOnCommand("ZINTER") + void testZsetIntersectWithAggregate() { + + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + zSetOps.add(key1, value1, 1.0); + zSetOps.add(key1, value2, 2.0); + zSetOps.add(key2, value2, 3.0); + + assertThat(zSetOps.intersectWithScores(key1, Collections.singletonList(key2), RedisZSetCommands.Aggregate.MIN)) + .contains(new DefaultTypedTuple<>(value2, 2d)); + + zSetOps.intersectAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MIN); + assertThat(zSetOps.score(key1, value2)).isCloseTo(2.0, offset(0.1)); + } + @ParameterizedRedisTest // DATAREDIS-746 + void testZsetIntersectWithAggregateWeights() { + + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + V value1 = valueFactory.instance(); + + zSetOps.add(key1, value1, 4.0); + zSetOps.add(key2, value1, 3.0); + + zSetOps.intersectAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MAX, + Weights.of(1, 2)); + + assertThat(zSetOps.score(key1, value1)).isCloseTo(6.0, offset(0.1)); + } + + @ParameterizedRedisTest // GH-2042 + @EnabledOnCommand("ZUNION") + void testZsetUnion() { + + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + zSetOps.add(key1, value1, 1.0); + zSetOps.add(key1, value2, 2.0); + zSetOps.add(key2, value2, 3.0); + + assertThat(zSetOps.union(key1, Collections.singletonList(key2))).containsOnly(value1, value2); + } + + @ParameterizedRedisTest // DATAREDIS-746, GH-2042 + @EnabledOnCommand("ZUNION") void testZsetUnionWithAggregate() { K key1 = keyFactory.instance(); @@ -475,6 +585,9 @@ public class DefaultZSetOperationsIntegrationTests { zSetOps.add(key1, value2, 2.0); zSetOps.add(key2, value2, 3.0); + assertThat(zSetOps.unionWithScores(key1, Collections.singletonList(key2))) + .containsOnly(new DefaultTypedTuple<>(value1, 1d), new DefaultTypedTuple<>(value2, 5d)); + zSetOps.unionAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MIN); assertThat(zSetOps.score(key1, value2)).isCloseTo(2.0, offset(0.1)); @@ -496,38 +609,4 @@ public class DefaultZSetOperationsIntegrationTests { assertThat(zSetOps.score(key1, value1)).isCloseTo(6.0, offset(0.1)); } - @ParameterizedRedisTest // DATAREDIS-746 - void testZsetIntersectWithAggregate() { - - K key1 = keyFactory.instance(); - K key2 = keyFactory.instance(); - - V value1 = valueFactory.instance(); - V value2 = valueFactory.instance(); - - zSetOps.add(key1, value1, 1.0); - zSetOps.add(key1, value2, 2.0); - zSetOps.add(key2, value2, 3.0); - - zSetOps.intersectAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MIN); - - assertThat(zSetOps.score(key1, value2)).isCloseTo(2.0, offset(0.1)); - } - - @ParameterizedRedisTest // DATAREDIS-746 - void testZsetIntersectWithAggregateWeights() { - - K key1 = keyFactory.instance(); - K key2 = keyFactory.instance(); - V value1 = valueFactory.instance(); - - zSetOps.add(key1, value1, 4.0); - zSetOps.add(key2, value1, 3.0); - - zSetOps.intersectAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MAX, - Weights.of(1, 2)); - - assertThat(zSetOps.score(key1, value1)).isCloseTo(6.0, offset(0.1)); - } - }