diff --git a/pom.xml b/pom.xml index 8657b1992a..00936eca2c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,13 @@ - + 4.0.0 org.springframework.data spring-data-redis - 2.6.0-SNAPSHOT + 2.6.0-2042-SNAPSHOT Spring Data Redis diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index eacddaa979..1a62dc7de9 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`). +* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`, `COPY`, `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 6ce7ac173c..9938b5cd36 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 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; @@ -81,7 +82,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco private final RedisSerializer serializer; private Converter bytesToString = new DeserializingConverter(); private Converter stringToBytes = new SerializingConverter(); - private SetConverter tupleToStringTuple = new SetConverter<>(new TupleConverter()); + private final TupleConverter tupleConverter = new TupleConverter(); + private SetConverter tupleToStringTuple = new SetConverter<>(tupleConverter); private SetConverter stringTupleToTuple = new SetConverter<>(new StringTupleConverter()); private ListConverter byteListToStringList = new ListConverter<>(bytesToString); private MapConverter byteMapToStringMap = new MapConverter<>(bytesToString); @@ -277,7 +279,6 @@ public Long decrBy(byte[] key, long value) { return convertAndReturn(delegate.decrBy(key, value), Converters.identityConverter()); } - /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#del(byte[][]) @@ -1412,6 +1413,127 @@ public Double zIncrBy(byte[] key, double increment, byte[] value) { 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[][]) @@ -1490,7 +1612,8 @@ public Set zRangeByScore(byte[] key, double min, double max) { */ @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()); } /* @@ -1562,7 +1685,8 @@ public Set zRevRangeByScore(byte[] key, Range range, Limit limit) { */ @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()); } /* @@ -1682,6 +1806,75 @@ public Double zScore(byte[] key, byte[] value) { return convertAndReturn(delegate.zScore(key, value), Converters.identityConverter()); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zMScore(byte[], byte[][]) + */ + @Override + public List zMScore(byte[] key, byte[]... values) { + 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[][]) @@ -1813,7 +2006,8 @@ public T eval(byte[] script, ReturnType returnType, int numKeys, byte[]... k */ @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()); } /* @@ -1822,7 +2016,8 @@ public T evalSha(String scriptSha1, ReturnType returnType, int numKeys, byte */ @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()); } // @@ -1909,6 +2104,7 @@ public String bRPopLPush(int timeout, String srcKey, String dstKey) { 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) @@ -1936,7 +2132,6 @@ public Long del(String... keys) { return del(serializeMulti(keys)); } - /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#unlink(java.lang.String[]) @@ -2812,6 +3007,126 @@ public Long zLexCount(byte[] key, Range range) { return delegate.zLexCount(key, range); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMin(byte[]) + */ + @Nullable + @Override + public Tuple zPopMin(byte[] key) { + return delegate.zPopMin(key); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMin(String) + */ + @Nullable + @Override + public StringTuple zPopMin(String key) { + return convertAndReturn(delegate.zPopMin(serialize(key)), tupleConverter); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMinMin(byte[], count) + */ + @Nullable + @Override + public Set zPopMin(byte[] key, long count) { + return delegate.zPopMin(key, count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMin(String, long) + */ + @Nullable + @Override + public Set zPopMin(String key, long count) { + return convertAndReturn(delegate.zPopMin(serialize(key), count), tupleToStringTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMin(byte[], long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public Tuple bZPopMin(byte[] key, long timeout, TimeUnit unit) { + return delegate.bZPopMin(key, timeout, unit); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMin(String, long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public StringTuple bZPopMin(String key, long timeout, TimeUnit unit) { + return convertAndReturn(delegate.bZPopMin(serialize(key), timeout, unit), tupleConverter); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(byte[]) + */ + @Nullable + @Override + public Tuple zPopMax(byte[] key) { + return delegate.zPopMax(key); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(String) + */ + @Nullable + @Override + public StringTuple zPopMax(String key) { + return convertAndReturn(delegate.zPopMax(serialize(key)), tupleConverter); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(byte[], long) + */ + @Nullable + @Override + public Set zPopMax(byte[] key, long count) { + return delegate.zPopMax(key, count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(String, long) + */ + @Nullable + @Override + public Set zPopMax(String key, long count) { + return convertAndReturn(delegate.zPopMax(serialize(key), count), tupleToStringTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMax(byte[], long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public Tuple bZPopMax(byte[] key, long timeout, TimeUnit unit) { + return delegate.bZPopMax(key, timeout, unit); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMax(String, long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public StringTuple bZPopMax(String key, long timeout, TimeUnit unit) { + return convertAndReturn(delegate.bZPopMax(serialize(key), timeout, unit), tupleConverter); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#zIncrBy(java.lang.String, double, java.lang.String) @@ -3012,6 +3327,15 @@ public Double zScore(String key, String value) { return zScore(serialize(key), serialize(value)); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zMScore(java.lang.String, java.lang.String[]) + */ + @Override + public List zMScore(String key, String... values) { + return zMScore(serialize(key), serializeMulti(values)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#zUnionStore(java.lang.String, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, int[], java.lang.String[]) @@ -3820,7 +4144,8 @@ public RecordId xAdd(StringRecord record, XAddOptions options) { */ @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()); } /* @@ -3857,7 +4182,8 @@ public String xGroupCreate(String key, ReadOffset readOffset, String group) { */ @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()); } /* @@ -3930,7 +4256,8 @@ public PendingMessagesSummary xPending(String key, String groupName) { @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()); } /* @@ -4227,7 +4554,8 @@ private T convertAndReturn(@Nullable Object value, Converter converter) { } 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/DefaultStringTuple.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringTuple.java index d53208e389..d50e8f09fc 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringTuple.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringTuple.java @@ -40,6 +40,18 @@ public DefaultStringTuple(byte[] value, String valueAsString, Double score) { } + /** + * Constructs a new DefaultStringTuple instance. + * + * @param valueAsString + * @param score + * @since 2.6 + */ + public DefaultStringTuple(String valueAsString, double score) { + super(valueAsString.getBytes(), score); + this.valueAsString = valueAsString; + } + /** * Constructs a new DefaultStringTuple instance. * diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultTuple.java b/src/main/java/org/springframework/data/redis/connection/DefaultTuple.java index 52dd426295..640c5bab53 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultTuple.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultTuple.java @@ -89,4 +89,14 @@ public int compareTo(Double o) { Double a = (o == null ? Double.valueOf(0.0d) : o); return d.compareTo(a); } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append(" [score=").append(score); + sb.append(", value=").append(value == null ? "null" : new String(value)); + sb.append(']'); + return sb.toString(); + } } 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 f26ba14cd3..07de3232cb 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -928,6 +928,48 @@ default Long zLexCount(byte[] key, Range range) { return zSetCommands().zLexCount(key, range); } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Tuple zPopMin(byte[] key) { + return zSetCommands().zPopMin(key); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zPopMin(byte[] key, long count) { + return zSetCommands().zPopMin(key, count); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Tuple bZPopMin(byte[] key, long timeout, TimeUnit unit) { + return zSetCommands().bZPopMin(key, timeout, unit); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Tuple zPopMax(byte[] key) { + return zSetCommands().zPopMax(key); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zPopMax(byte[] key, long count) { + return zSetCommands().zPopMax(key, count); + } + + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Tuple bZPopMax(byte[] key, long timeout, TimeUnit unit) { + return zSetCommands().bZPopMax(key, timeout, unit); + } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated @@ -935,6 +977,27 @@ default Long zCount(byte[] key, Range range) { 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 @@ -942,6 +1005,34 @@ default Double zIncrBy(byte[] key, double increment, byte[] value) { 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 @@ -1089,6 +1180,41 @@ default Double zScore(byte[] key, byte[] value) { return zSetCommands().zScore(key, value); } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default List zMScore(byte[] key, byte[]... values) { + 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/ReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java index ed307120c2..f9723e82a6 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java @@ -15,23 +15,24 @@ */ package org.springframework.data.redis.connection; +import static org.springframework.data.redis.connection.ReactiveRedisConnection.*; + import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Function; import org.reactivestreams.Publisher; + import org.springframework.data.domain.Range; import org.springframework.data.domain.Sort.Direction; -import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse; -import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand; -import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScanCommand; -import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse; import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate; import org.springframework.data.redis.connection.RedisZSetCommands.Limit; import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; @@ -159,8 +160,7 @@ public ZAddCommand incr() { } /** - * Applies {@literal GT} mode. Constructs a new command - * instance with all previously configured properties. + * Applies {@literal GT} mode. Constructs a new command instance with all previously configured properties. * * @return a new {@link ZAddCommand} with {@literal incr} applied. * @since 2.5 @@ -170,8 +170,7 @@ public ZAddCommand gt() { } /** - * Applies {@literal LT} mode. Constructs a new command - * instance with all previously configured properties. + * Applies {@literal LT} mode. Constructs a new command instance with all previously configured properties. * * @return a new {@link ZAddCommand} with {@literal incr} applied. * @since 2.5 @@ -202,7 +201,6 @@ public boolean isIncr() { } /** - * * @return {@literal true} if {@literal GT} is set. * @since 2.5 */ @@ -292,7 +290,7 @@ private ZRemCommand(@Nullable ByteBuffer key, List values) { * Creates a new {@link ZRemCommand} given a {@link Tuple}. * * @param value must not be {@literal null}. - * @return a new {@link ZAddCommand} for {@link Tuple}. + * @return a new {@link ZRemCommand} for {@link Tuple}. */ public static ZRemCommand values(ByteBuffer value) { @@ -305,7 +303,7 @@ public static ZRemCommand values(ByteBuffer value) { * Creates a new {@link ZRemCommand} given a {@link Collection} of {@link Tuple}. * * @param values must not be {@literal null}. - * @return a new {@link ZAddCommand} for {@link Tuple}. + * @return a new {@link ZRemCommand} for {@link Tuple}. */ public static ZRemCommand values(Collection values) { @@ -1240,6 +1238,293 @@ default Mono zLexCount(ByteBuffer key, Range range) { */ Flux> zLexCount(Publisher commands); + /** + * @author Mark Paluch + */ + enum PopDirection { + MIN, MAX + } + + /** + * {@code ZPOPMIN}/{@literal ZPOPMAX} command parameters. + * + * @author Mark Paluch + * @since 2.6 + * @see Redis Documentation: ZPOPMIN + * @see Redis Documentation: ZPOPMAX + */ + class ZPopCommand extends KeyCommand { + + private final PopDirection direction; + + private final long count; + + private ZPopCommand(PopDirection direction, @Nullable ByteBuffer key, long count) { + + super(key); + this.count = count; + this.direction = direction; + } + + /** + * Creates a new {@link ZPopCommand} for min pop ({@literal ZPOPMIN}). + * + * @return a new {@link ZPopCommand} for min pop ({@literal ZPOPMIN}). + */ + public static ZPopCommand min() { + return new ZPopCommand(PopDirection.MIN, null, 1); + } + + /** + * Creates a new {@link ZPopCommand} for max pop ({@literal ZPOPMAX}). + * + * @return a new {@link ZPopCommand} for max pop ({@literal ZPOPMAX}). + */ + public static ZPopCommand max() { + return new ZPopCommand(PopDirection.MAX, null, 1); + } + + /** + * Applies the {@literal key}. Constructs a new command instance with all previously configured properties. + * + * @param key must not be {@literal null}. + * @return a new {@link ZPopCommand} with {@literal value} applied. + */ + public ZPopCommand from(ByteBuffer key) { + + Assert.notNull(key, "Key must not be null!"); + + return new ZPopCommand(direction, key, count); + } + + /** + * Applies the {@literal key}. Constructs a new command instance with all previously configured properties. + * + * @param count + * @return a new {@link ZPopCommand} with {@literal value} applied. + */ + public ZPopCommand count(long count) { + return new ZPopCommand(direction, getKey(), count); + } + + /** + * @return never {@literal null}. + */ + public PopDirection getDirection() { + return direction; + } + + public long getCount() { + return count; + } + } + + /** + * {@code BZPOPMIN}/{@literal BZPOPMAX} command parameters. + * + * @author Mark Paluch + * @since 2.6 + * @see Redis Documentation: BZPOPMIN + * @see Redis Documentation: BZPOPMAX + */ + class BZPopCommand extends KeyCommand { + + private final PopDirection direction; + + private final Duration timeout; + + private final long count; + + private BZPopCommand(@Nullable ByteBuffer key, Duration timeout, long count, PopDirection direction) { + + super(key); + this.count = count; + this.timeout = timeout; + this.direction = direction; + } + + /** + * Creates a new {@link BZPopCommand} for min pop ({@literal ZPOPMIN}). + * + * @return a new {@link BZPopCommand} for min pop ({@literal ZPOPMIN}). + */ + public static BZPopCommand min() { + return new BZPopCommand(null, null, 0, PopDirection.MIN); + } + + /** + * Creates a new {@link BZPopCommand} for max pop ({@literal ZPOPMAX}). + * + * @return a new {@link BZPopCommand} for max pop ({@literal ZPOPMAX}). + */ + public static BZPopCommand max() { + return new BZPopCommand(null, null, 0, PopDirection.MAX); + } + + /** + * Applies the {@literal key}. Constructs a new command instance with all previously configured properties. + * + * @param key must not be {@literal null}. + * @return a new {@link BZPopCommand} with {@literal value} applied. + */ + public BZPopCommand from(ByteBuffer key) { + + Assert.notNull(key, "Key must not be null!"); + + return new BZPopCommand(key, timeout, count, direction); + } + + /** + * Applies the {@literal key}. Constructs a new command instance with all previously configured properties. + * + * @param count + * @return a new {@link BZPopCommand} with {@literal value} applied. + */ + public BZPopCommand count(long count) { + return new BZPopCommand(getKey(), timeout, count, direction); + } + + /** + * Applies a {@link Duration timeout}. Constructs a new command instance with all previously configured properties. + * + * @param timeout must not be {@literal null}. + * @return a new {@link BZPopCommand} with {@link Duration timeout} applied. + */ + public BZPopCommand blockingFor(Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null!"); + + return new BZPopCommand(getKey(), timeout, count, direction); + } + + /** + * @return never {@literal null}. + */ + public PopDirection getDirection() { + return direction; + } + + public Duration getTimeout() { + return timeout; + } + + public long getCount() { + return count; + } + } + + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZPOPMIN + */ + default Mono zPopMin(ByteBuffer key) { + return zPop(Mono.just(ZPopCommand.min().from(key))).map(CommandResponse::getOutput).flatMap(Flux::next).next(); + } + + /** + * Remove and return {@code count} values with their score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return + * @since 2.6 + * @see Redis Documentation: ZPOPMIN + */ + default Flux zPopMin(ByteBuffer key, long count) { + return zPop(Mono.just(ZPopCommand.min().from(key).count(count))).map(CommandResponse::getOutput) + .flatMap(Function.identity()); + } + + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout must not be {@literal null}. + * @return + * @throws IllegalArgumentException if the timeout is {@literal null} or negative. + * @since 2.6 + * @see Redis Documentation: BZPOPMIN + */ + default Mono bZPopMin(ByteBuffer key, Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null"); + Assert.isTrue(!timeout.isNegative(), "Timeout must not be negative"); + + return bZPop(Mono.just(BZPopCommand.min().from(key).blockingFor(timeout))).map(CommandResponse::getOutput) + .flatMap(Flux::next).next(); + } + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZPOPMAX + */ + default Mono zPopMax(ByteBuffer key) { + return zPop(Mono.just(ZPopCommand.max().from(key))).map(CommandResponse::getOutput).flatMap(Flux::next).next(); + } + + /** + * Remove and return {@code count} values with their score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return + * @since 2.6 + * @see Redis Documentation: ZPOPMAX + */ + default Flux zPopMax(ByteBuffer key, long count) { + return zPop(Mono.just(ZPopCommand.max().from(key).count(count))).map(CommandResponse::getOutput) + .flatMap(Function.identity()); + } + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout must not be {@literal null}. + * @return + * @throws IllegalArgumentException if the timeout is {@literal null} or negative. + * @since 2.6 + * @see Redis Documentation: BZPOPMAX + */ + default Mono bZPopMax(ByteBuffer key, Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null"); + Assert.isTrue(!timeout.isNegative(), "Timeout must not be negative"); + + return bZPop(Mono.just(BZPopCommand.max().from(key).blockingFor(timeout))).map(CommandResponse::getOutput) + .flatMap(Flux::next).next(); + } + + /** + * Remove and return elements from sorted set at {@link ByteBuffer keyCommand#getKey()}. + * + * @param commands must not be {@literal null}. + * @return + * @see Redis Documentation: ZPOPMIN + * @see Redis Documentation: ZPOPMAX + */ + Flux>> zPop(Publisher commands); + + /** + * Remove and return elements from sorted set at {@link ByteBuffer keyCommand#getKey()}. + * + * @param commands must not be {@literal null}. + * @return + * @see Redis Documentation: ZPOPMIN + * @see Redis Documentation: ZPOPMAX + */ + Flux>> bZPop(Publisher commands); + /** * Get the size of sorted set with {@literal key}. * @@ -1255,7 +1540,7 @@ default Mono zCard(ByteBuffer key) { } /** - * Get the size of sorted set with {@link KeyCommand#getKey()}. + * Get the size of sorted set with {@linByteBuffer keyCommand#getKey()}. * * @param commands must not be {@literal null}. * @return @@ -1283,7 +1568,7 @@ private ZScoreCommand(@Nullable ByteBuffer key, ByteBuffer value) { * Creates a new {@link ZScoreCommand} given a {@link ByteBuffer member}. * * @param member must not be {@literal null}. - * @return a new {@link ZScoreCommand} for {@link Range}. + * @return a new {@link ZScoreCommand} for {@link ByteBuffer member}. */ public static ZScoreCommand scoreOf(ByteBuffer member) { @@ -1339,6 +1624,99 @@ default Mono zScore(ByteBuffer key, ByteBuffer value) { */ Flux> zScore(Publisher commands); + /** + * {@code ZMSCORE} command parameters. + * + * @author Mark Paluch + * @since 2.6 + * @see Redis Documentation: ZMSCORE + */ + class ZMScoreCommand extends KeyCommand { + + private final Collection values; + + private ZMScoreCommand(@Nullable ByteBuffer key, Collection values) { + + super(key); + + this.values = values; + } + + /** + * Creates a new {@link ZMScoreCommand} given a {@link ByteBuffer member}. + * + * @param member must not be {@literal null}. + * @return a new {@link ZMScoreCommand} for {@link ByteBuffer}. + */ + public static ZMScoreCommand scoreOf(ByteBuffer member) { + + Assert.notNull(member, "Member must not be null!"); + + return new ZMScoreCommand(null, Collections.singletonList(member)); + } + + /** + * Creates a new {@link ZMScoreCommand} given a {@link List members}. + * + * @param member must not be {@literal null}. + * @return a new {@link ZMScoreCommand} for {@link List} of members. + */ + public static ZMScoreCommand scoreOf(Collection members) { + + Assert.notNull(members, "Members must not be null!"); + + return new ZMScoreCommand(null, members); + } + + /** + * Applies the {@literal key}. Constructs a new command instance with all previously configured properties. + * + * @param key must not be {@literal null}. + * @return a new {@link ZMScoreCommand} with {@literal key} applied. + */ + public ZMScoreCommand forKey(ByteBuffer key) { + + Assert.notNull(key, "Key must not be null!"); + + return new ZMScoreCommand(key, values); + } + + /** + * @return + */ + public Collection getValues() { + return values; + } + } + + /** + * Get the scores of elements with {@literal values} from sorted set with key {@literal key}. + * + * @param key must not be {@literal null}. + * @param values must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZMSCORE + */ + default Mono> zMScore(ByteBuffer key, Collection values) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(values, "Values must not be null!"); + + return zMScore(Mono.just(ZMScoreCommand.scoreOf(values).forKey(key))).next().map(MultiValueResponse::getOutput); + } + + /** + * Get the scores of elements with {@link ZMScoreCommand#getValues()} from sorted set with key + * {@link ZMScoreCommand#getKey()} + * + * @param commands must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZMSCORE + */ + Flux> zMScore(Publisher commands); + /** * {@code ZREMRANGEBYRANK} command parameters. * @@ -1564,57 +1942,229 @@ default Mono zRemRangeByLex(ByteBuffer key, Range range) { Flux> zRemRangeByLex(Publisher commands); /** - * {@code ZUNIONSTORE} command parameters. + * {@code ZDIFF} command parameters. * - * @author Christoph Strobl - * @see Redis Documentation: ZUNIONSTORE + * @author Mark Paluch + * @since 2.6 + * @see Redis Documentation: ZDIFF */ - class ZUnionStoreCommand extends KeyCommand { - - private final List sourceKeys; - private final List weights; - private final @Nullable Aggregate aggregateFunction; + class ZDiffCommand implements Command { - private ZUnionStoreCommand(@Nullable ByteBuffer key, List sourceKeys, List weights, - @Nullable Aggregate aggregate) { + private final List keys; - super(key); - this.sourceKeys = sourceKeys; - this.weights = weights; - this.aggregateFunction = aggregate; + private ZDiffCommand(List keys) { + this.keys = keys; } /** - * Creates a new {@link ZUnionStoreCommand} given a {@link List} of keys. + * Creates a new {@link ZDiffCommand} given a {@link Collection} of keys. * * @param keys must not be {@literal null}. - * @return a new {@link ZUnionStoreCommand} for {@link Range}. + * @return a new {@link ZDiffCommand} for a {@link Collection} of values. */ - public static ZUnionStoreCommand sets(List keys) { + public static ZDiffCommand sets(Collection keys) { Assert.notNull(keys, "Keys must not be null!"); - return new ZUnionStoreCommand(null, new ArrayList<>(keys), Collections.emptyList(), null); + return new ZDiffCommand(new ArrayList<>(keys)); } - /** - * Applies the {@link List} of weights. Constructs a new command instance with all previously configured properties. - * - * @param weights must not be {@literal null}. - * @return a new {@link ZUnionStoreCommand} with {@literal weights} applied. + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveRedisConnection.Command#getKey() */ - public ZUnionStoreCommand applyWeights(List weights) { - return new ZUnionStoreCommand(getKey(), sourceKeys, weights, aggregateFunction); + @Override + @Nullable + public ByteBuffer getKey() { + return null; + } + + /** + * @return + */ + public List getKeys() { + return keys; + } + } + + /** + * Diff sorted {@literal sets}. + * + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + default Flux zDiff(List sets) { + return zDiff(Mono.just(ZDiffCommand.sets(sets))).flatMap(CommandResponse::getOutput); + } + + /** + * Diff sorted {@literal sets}. + * + * @param commands + * @return + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + Flux>> zDiff(Publisher commands); + + /** + * Diff sorted {@literal sets}. + * + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + default Flux zDiffWithScores(List sets) { + return zDiffWithScores(Mono.just(ZDiffCommand.sets(sets))).flatMap(CommandResponse::getOutput); + } + + /** + * Diff sorted {@literal sets}. + * + * @param commands + * @return + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + Flux>> zDiffWithScores(Publisher commands); + + /** + * {@code ZDIFFSTORE} command parameters. + * + * @author Mark Paluch + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE + */ + class ZDiffStoreCommand extends KeyCommand { + + private final List sourceKeys; + + private ZDiffStoreCommand(@Nullable ByteBuffer key, List sourceKeys) { + + super(key); + + this.sourceKeys = sourceKeys; + } + + /** + * Creates a new {@link ZDiffStoreCommand} given a {@link Collection} of keys. + * + * @param keys must not be {@literal null}. + * @return a new {@link ZDiffStoreCommand} for a {@link Collection} of values. + */ + public static ZDiffStoreCommand sourceKeys(Collection keys) { + + Assert.notNull(keys, "Keys must not be null!"); + + return new ZDiffStoreCommand(null, new ArrayList<>(keys)); + } + + /** + * Applies the {@literal key} at which the result is stored. Constructs a new command instance with all previously + * configured properties. + * + * @param key must not be {@literal null}. + * @return a new {@link ZDiffStoreCommand} with {@literal key} applied. + */ + public ZDiffStoreCommand storeAs(ByteBuffer key) { + + Assert.notNull(key, "Key must not be null!"); + + return new ZDiffStoreCommand(key, sourceKeys); + } + + /** + * @return + */ + public List getSourceKeys() { + return sourceKeys; + } + } + + /** + * Diff sorted {@literal sets} and store result in destination {@literal destinationKey}. + * + * @param destinationKey must not be {@literal null}. + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE + */ + default Mono zDiffStore(ByteBuffer destinationKey, List sets) { + + Assert.notNull(destinationKey, "DestinationKey must not be null!"); + Assert.notNull(sets, "Sets must not be null!"); + + return zDiffStore(Mono.just(ZDiffStoreCommand.sourceKeys(sets).storeAs(destinationKey))).next() + .map(NumericResponse::getOutput); + } + + /** + * Diff sorted {@literal sets} and store result in destination {@literal destinationKey}. + * + * @param commands + * @return + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE + */ + Flux> zDiffStore(Publisher commands); + + /** + * {@code ZINTER}/{@code ZUNION} command parameters. + * + * @author Christoph Strobl + * @author Mark Paluch + * @since 2.6 + * @see Redis Documentation: ZINTER + * @see Redis Documentation: ZUNION + */ + class ZAggregateCommand implements Command { + + private final List sourceKeys; + private final List weights; + private final @Nullable Aggregate aggregateFunction; + + private ZAggregateCommand(List sourceKeys, List weights, @Nullable Aggregate aggregate) { + + this.sourceKeys = sourceKeys; + this.weights = weights; + this.aggregateFunction = aggregate; + } + + /** + * Creates a new {@link ZAggregateCommand} given a {@link List} of keys. + * + * @param keys must not be {@literal null}. + * @return a new {@link ZAggregateCommand} for {@link Range}. + */ + public static ZAggregateCommand sets(List keys) { + + Assert.notNull(keys, "Keys must not be null!"); + + return new ZAggregateCommand(new ArrayList<>(keys), Collections.emptyList(), null); + } + + /** + * Applies the {@link List} of weights. Constructs a new command instance with all previously configured properties. + * + * @param weights must not be {@literal null}. + * @return a new {@link ZAggregateCommand} with {@literal weights} applied. + */ + public ZAggregateCommand applyWeights(List weights) { + return new ZAggregateCommand(sourceKeys, weights, aggregateFunction); } /** * Applies the {@link Weights}. Constructs a new command instance with all previously configured properties. * * @param weights must not be {@literal null}. - * @return a new {@link ZUnionStoreCommand} with {@literal weights} applied. + * @return a new {@link ZAggregateCommand} with {@literal weights} applied. * @since 2.1 */ - public ZUnionStoreCommand applyWeights(Weights weights) { + public ZAggregateCommand applyWeights(Weights weights) { return applyWeights(weights.toList()); } @@ -1623,11 +2173,109 @@ public ZUnionStoreCommand applyWeights(Weights weights) { * properties. * * @param aggregateFunction can be {@literal null}. - * @return a new {@link ZUnionStoreCommand} with {@link Aggregate} applied. + * @return a new {@link ZAggregateStoreCommand} with {@link Aggregate} applied. */ - public ZUnionStoreCommand aggregateUsing(@Nullable Aggregate aggregateFunction) { + public ZAggregateCommand aggregateUsing(@Nullable Aggregate aggregateFunction) { + + return new ZAggregateCommand(sourceKeys, weights, aggregateFunction); + } + + @Nullable + @Override + public ByteBuffer getKey() { + return null; + } + + /** + * @return never {@literal null}. + */ + public List getSourceKeys() { + return sourceKeys; + } + + /** + * @return never {@literal null}. + */ + public List getWeights() { + return weights; + } + + /** + * @return never {@literal null}. + */ + public Optional getAggregateFunction() { + return Optional.ofNullable(aggregateFunction); + } + } + + /** + * {@code ZINTERSTORE}/{@code ZUNIONSTORE} command parameters. + * + * @author Christoph Strobl + * @author Mark Paluch + * @since 2.6 + * @see Redis Documentation: ZINTERSTORE + * @see Redis Documentation: ZUNIONSTORE + */ + class ZAggregateStoreCommand extends KeyCommand { + + private final List sourceKeys; + private final List weights; + private final @Nullable Aggregate aggregateFunction; + + private ZAggregateStoreCommand(@Nullable ByteBuffer key, List sourceKeys, List weights, + @Nullable Aggregate aggregate) { + + super(key); + this.sourceKeys = sourceKeys; + this.weights = weights; + this.aggregateFunction = aggregate; + } + + /** + * Creates a new {@link ZAggregateStoreCommand} given a {@link List} of keys. + * + * @param keys must not be {@literal null}. + * @return a new {@link ZAggregateStoreCommand} for {@link Range}. + */ + public static ZAggregateStoreCommand sets(List keys) { + + Assert.notNull(keys, "Keys must not be null!"); + + return new ZAggregateStoreCommand(null, new ArrayList<>(keys), Collections.emptyList(), null); + } + + /** + * Applies the {@link List} of weights. Constructs a new command instance with all previously configured properties. + * + * @param weights must not be {@literal null}. + * @return a new {@link ZAggregateStoreCommand} with {@literal weights} applied. + */ + public ZAggregateStoreCommand applyWeights(List weights) { + return new ZAggregateStoreCommand(getKey(), sourceKeys, weights, aggregateFunction); + } + + /** + * Applies the {@link Weights}. Constructs a new command instance with all previously configured properties. + * + * @param weights must not be {@literal null}. + * @return a new {@link ZAggregateStoreCommand} with {@literal weights} applied. + * @since 2.1 + */ + public ZAggregateStoreCommand applyWeights(Weights weights) { + return applyWeights(weights.toList()); + } - return new ZUnionStoreCommand(getKey(), sourceKeys, weights, aggregateFunction); + /** + * Applies a specific {@link Aggregate} function. Constructs a new command instance with all previously configured + * properties. + * + * @param aggregateFunction can be {@literal null}. + * @return a new {@link ZAggregateStoreCommand} with {@link Aggregate} applied. + */ + public ZAggregateStoreCommand aggregateUsing(@Nullable Aggregate aggregateFunction) { + + return new ZAggregateStoreCommand(getKey(), sourceKeys, weights, aggregateFunction); } /** @@ -1635,65 +2283,254 @@ public ZUnionStoreCommand aggregateUsing(@Nullable Aggregate aggregateFunction) * configured properties. * * @param key must not be {@literal null}. - * @return a new {@link ZUnionStoreCommand} with {@literal key} applied. + * @return a new {@link ZAggregateStoreCommand} with {@literal key} applied. */ - public ZUnionStoreCommand storeAs(ByteBuffer key) { + public ZAggregateStoreCommand storeAs(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); - return new ZUnionStoreCommand(key, sourceKeys, weights, aggregateFunction); + return new ZAggregateStoreCommand(key, sourceKeys, weights, aggregateFunction); + } + + /** + * @return never {@literal null}. + */ + public List getSourceKeys() { + return sourceKeys; + } + + /** + * @return never {@literal null}. + */ + public List getWeights() { + return weights; } /** * @return never {@literal null}. */ - public List getSourceKeys() { - return sourceKeys; - } + public Optional getAggregateFunction() { + return Optional.ofNullable(aggregateFunction); + } + } + + /** + * Intersect sorted {@literal sets}. + * + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + default Flux zInter(List sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + return zInter(Mono.just(ZAggregateCommand.sets(sets))).flatMap(CommandResponse::getOutput); + } + + /** + * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and apply weights to individual sets. + * + * @param commands + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + Flux>> zInter(Publisher commands); + + /** + * Intersect sorted {@literal sets}. + * + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + default Flux zInterWithScores(List sets) { + return zInterWithScores(sets, Collections.emptyList()); + } + + /** + * Intersect sorted {@literal sets} and apply weights to individual sets. + * + * @param sets must not be {@literal null}. + * @param weights must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + default Flux zInterWithScores(List sets, List weights) { + return zInterWithScores(sets, weights, null); + } + + /** + * Intersect sorted {@literal sets} and apply weights to individual sets. + * + * @param sets must not be {@literal null}. + * @param weights must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + default Flux zInterWithScores(List sets, Weights weights) { + return zInterWithScores(sets, weights, null); + } + + /** + * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and apply weights to individual sets. + * + * @param sets must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param aggregateFunction can be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + default Flux zInterWithScores(List sets, List weights, + @Nullable Aggregate aggregateFunction) { + + Assert.notNull(sets, "Sets must not be null!"); + + return zInterWithScores( + Mono.just(ZAggregateCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights))) + .flatMap(CommandResponse::getOutput); + } + + /** + * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and apply weights to individual sets. + * + * @param sets must not be {@literal null}. + * @param weights must not be {@literal null}. + * @param aggregateFunction can be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + default Flux zInterWithScores(List sets, Weights weights, @Nullable Aggregate aggregateFunction) { + + Assert.notNull(sets, "Sets must not be null!"); + + return zInterWithScores( + Mono.just(ZAggregateCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights))) + .flatMap(CommandResponse::getOutput); + } + + /** + * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and apply weights to individual sets. + * + * @param commands + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + Flux>> zInterWithScores( + Publisher commands); + + /** + * {@code ZINTERSTORE} command parameters. + * + * @author Christoph Strobl + * @see Redis Documentation: ZINTERSTORE + */ + class ZInterStoreCommand extends ZAggregateStoreCommand { + + private ZInterStoreCommand(ByteBuffer key, List sourceKeys, List weights, + @Nullable Aggregate aggregate) { + super(key, sourceKeys, weights, aggregate); + } + + /** + * Creates a new {@link ZInterStoreCommand} given a {@link List} of keys. + * + * @param keys must not be {@literal null}. + * @return a new {@link ZInterStoreCommand} for {@link List} of keys. + */ + public static ZInterStoreCommand sets(List keys) { + + Assert.notNull(keys, "Keys must not be null!"); + + return new ZInterStoreCommand(null, new ArrayList<>(keys), Collections.emptyList(), null); + } + + /** + * Applies the {@link Collection} of weights. Constructs a new command instance with all previously configured + * properties. + * + * @param weights must not be {@literal null}. + * @return a new {@link ZInterStoreCommand} with {@literal weights} applied. + */ + public ZInterStoreCommand applyWeights(List weights) { + return new ZInterStoreCommand(getKey(), getSourceKeys(), weights, getAggregateFunction().orElse(null)); + } + + /** + * Applies the {@link Weights}. Constructs a new command instance with all previously configured properties. + * + * @param weights must not be {@literal null}. + * @return a new {@link ZInterStoreCommand} with {@literal weights} applied. + * @since 2.1 + */ + public ZInterStoreCommand applyWeights(Weights weights) { + return applyWeights(weights.toList()); + } + + /** + * Applies a specific {@link Aggregate} function. Constructs a new command instance with all previously configured + * properties. + * + * @param aggregateFunction can be {@literal null}. + * @return a new {@link ZInterStoreCommand} with {@link Aggregate} applied. + */ + public ZInterStoreCommand aggregateUsing(@Nullable Aggregate aggregateFunction) { - /** - * @return never {@literal null}. - */ - public List getWeights() { - return weights; + return new ZInterStoreCommand(getKey(), getSourceKeys(), getWeights(), aggregateFunction); } /** - * @return never {@literal null}. + * Applies the {@literal key} at which the result is stored. Constructs a new command instance with all previously + * configured properties. + * + * @param key must not be {@literal null}. + * @return a new {@link ZInterStoreCommand} with {@literal key} applied. */ - public Optional getAggregateFunction() { - return Optional.ofNullable(aggregateFunction); + public ZInterStoreCommand storeAs(ByteBuffer key) { + + Assert.notNull(key, "Key must not be null!"); + + return new ZInterStoreCommand(key, getSourceKeys(), getWeights(), getAggregateFunction().orElse(null)); } + } /** - * Union sorted {@literal sets} and store result in destination {@literal destinationKey}. + * Intersect sorted {@literal sets} and store result in destination {@literal destinationKey}. * * @param destinationKey must not be {@literal null}. * @param sets must not be {@literal null}. * @return - * @see Redis Documentation: ZUNIONSTORE + * @see Redis Documentation: ZINTERSTORE */ - default Mono zUnionStore(ByteBuffer destinationKey, List sets) { - return zUnionStore(destinationKey, sets, Collections.emptyList()); + default Mono zInterStore(ByteBuffer destinationKey, List sets) { + return zInterStore(destinationKey, sets, Collections.emptyList()); } /** - * Union sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to + * Intersect sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to * individual sets. * * @param destinationKey must not be {@literal null}. * @param sets must not be {@literal null}. * @param weights must not be {@literal null}. * @return - * @see Redis Documentation: ZUNIONSTORE + * @see Redis Documentation: ZINTERSTORE */ - default Mono zUnionStore(ByteBuffer destinationKey, List sets, List weights) { - return zUnionStore(destinationKey, sets, weights, null); + default Mono zInterStore(ByteBuffer destinationKey, List sets, List weights) { + return zInterStore(destinationKey, sets, weights, null); } /** - * Union sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to + * Intersect sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to * individual sets. * * @param destinationKey must not be {@literal null}. @@ -1701,120 +2538,225 @@ default Mono zUnionStore(ByteBuffer destinationKey, List sets, * @param weights must not be {@literal null}. * @return * @since 2.1 - * @see Redis Documentation: ZUNIONSTORE + * @see Redis Documentation: ZINTERSTORE */ - default Mono zUnionStore(ByteBuffer destinationKey, List sets, Weights weights) { - return zUnionStore(destinationKey, sets, weights, null); + default Mono zInterStore(ByteBuffer destinationKey, List sets, Weights weights) { + return zInterStore(destinationKey, sets, weights, null); } /** - * Union sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination + * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination * {@literal destinationKey} and apply weights to individual sets. * * @param destinationKey must not be {@literal null}. * @param sets must not be {@literal null}. - * @param weights can be {@literal null}. + * @param weights must not be {@literal null}. * @param aggregateFunction can be {@literal null}. * @return - * @see Redis Documentation: ZUNIONSTORE + * @see Redis Documentation: ZINTERSTORE */ - default Mono zUnionStore(ByteBuffer destinationKey, List sets, List weights, + default Mono zInterStore(ByteBuffer destinationKey, List sets, List weights, @Nullable Aggregate aggregateFunction) { Assert.notNull(destinationKey, "DestinationKey must not be null!"); Assert.notNull(sets, "Sets must not be null!"); - return zUnionStore(Mono.just( - ZUnionStoreCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights).storeAs(destinationKey))) + return zInterStore(Mono.just( + ZInterStoreCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights).storeAs(destinationKey))) .next().map(NumericResponse::getOutput); } /** - * Union sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination + * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination * {@literal destinationKey} and apply weights to individual sets. * * @param destinationKey must not be {@literal null}. * @param sets must not be {@literal null}. - * @param weights can be {@literal null}. + * @param weights must not be {@literal null}. * @param aggregateFunction can be {@literal null}. * @return * @since 2.1 - * @see Redis Documentation: ZUNIONSTORE + * @see Redis Documentation: ZINTERSTORE */ - default Mono zUnionStore(ByteBuffer destinationKey, List sets, Weights weights, + default Mono zInterStore(ByteBuffer destinationKey, List sets, Weights weights, @Nullable Aggregate aggregateFunction) { Assert.notNull(destinationKey, "DestinationKey must not be null!"); Assert.notNull(sets, "Sets must not be null!"); - return zUnionStore(Mono.just( - ZUnionStoreCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights).storeAs(destinationKey))) + return zInterStore(Mono.just( + ZInterStoreCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights).storeAs(destinationKey))) .next().map(NumericResponse::getOutput); } /** - * Union sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination + * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination * {@literal destinationKey} and apply weights to individual sets. * * @param commands * @return - * @see Redis Documentation: ZUNIONSTORE + * @see Redis Documentation: ZINTERSTORE */ - Flux> zUnionStore(Publisher commands); + Flux> zInterStore(Publisher commands); /** - * {@code ZINTERSTORE} command parameters. + * Union sorted {@literal sets}. * - * @author Christoph Strobl - * @see Redis Documentation: ZINTERSTORE + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZUNION */ - class ZInterStoreCommand extends KeyCommand { + default Flux zUnion(List sets) { - private final List sourceKeys; - private final List weights; - private final @Nullable Aggregate aggregateFunction; + Assert.notNull(sets, "Sets must not be null!"); - private ZInterStoreCommand(ByteBuffer key, List sourceKeys, List weights, + return zUnion(Mono.just(ZAggregateCommand.sets(sets))).flatMap(CommandResponse::getOutput); + } + + /** + * Union sorted {@literal sets}. + * + * @param sets must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + default Flux zUnionWithScores(List sets) { + return zUnionWithScores(sets, Collections.emptyList()); + } + + /** + * Union sorted {@literal sets} and apply weights to individual sets. + * + * @param sets must not be {@literal null}. + * @param weights must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + default Flux zUnionWithScores(List sets, List weights) { + return zUnionWithScores(sets, weights, null); + } + + /** + * Union sorted {@literal sets} and apply weights to individual sets. + * + * @param sets must not be {@literal null}. + * @param weights must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + default Flux zUnionWithScores(List sets, Weights weights) { + return zUnionWithScores(sets, weights, null); + } + + /** + * Union sorted {@literal sets} by applying {@literal aggregateFunction} and apply weights to individual sets. + * + * @param sets must not be {@literal null}. + * @param weights can be {@literal null}. + * @param aggregateFunction can be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + default Flux zUnionWithScores(List sets, List weights, + @Nullable Aggregate aggregateFunction) { + + Assert.notNull(sets, "Sets must not be null!"); + + return zUnionWithScores( + Mono.just(ZAggregateCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights))) + .flatMap(CommandResponse::getOutput); + } + + /** + * Union sorted {@literal sets} by applying {@literal aggregateFunction} and apply weights to individual sets. + * + * @param sets must not be {@literal null}. + * @param weights can be {@literal null}. + * @param aggregateFunction can be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + default Flux zUnionWithScores(List sets, Weights weights, @Nullable Aggregate aggregateFunction) { + + Assert.notNull(sets, "Sets must not be null!"); + + return zUnionWithScores( + Mono.just(ZAggregateCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights))) + .flatMap(CommandResponse::getOutput); + } + + /** + * Union sorted {@literal sets} by applying {@literal aggregateFunction} and apply weights to individual sets. + * + * @param commands + * @return + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + Flux>> zUnionWithScores( + Publisher commands); + + /** + * Union sorted {@literal sets} by applying {@literal aggregateFunction} and apply weights to individual sets. + * + * @param commands + * @return + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + Flux>> zUnion(Publisher commands); + + /** + * {@code ZUNIONSTORE} command parameters. + * + * @author Christoph Strobl + * @see Redis Documentation: ZUNIONSTORE + */ + class ZUnionStoreCommand extends ZAggregateStoreCommand { + + private ZUnionStoreCommand(@Nullable ByteBuffer key, List sourceKeys, List weights, @Nullable Aggregate aggregate) { - super(key); - this.sourceKeys = sourceKeys; - this.weights = weights; - this.aggregateFunction = aggregate; + super(key, sourceKeys, weights, aggregate); } /** - * Creates a new {@link ZInterStoreCommand} given a {@link List} of keys. + * Creates a new {@link ZUnionStoreCommand} given a {@link List} of keys. * * @param keys must not be {@literal null}. - * @return a new {@link ZInterStoreCommand} for {@link Range}. + * @return a new {@link ZUnionStoreCommand} for {@link Range}. */ - public static ZInterStoreCommand sets(List keys) { + public static ZUnionStoreCommand sets(List keys) { Assert.notNull(keys, "Keys must not be null!"); - return new ZInterStoreCommand(null, new ArrayList<>(keys), Collections.emptyList(), null); + return new ZUnionStoreCommand(null, new ArrayList<>(keys), Collections.emptyList(), null); } /** - * Applies the {@link Collection} of weights. Constructs a new command instance with all previously configured - * properties. + * Applies the {@link List} of weights. Constructs a new command instance with all previously configured properties. * * @param weights must not be {@literal null}. - * @return a new {@link ZInterStoreCommand} with {@literal weights} applied. + * @return a new {@link ZUnionStoreCommand} with {@literal weights} applied. */ - public ZInterStoreCommand applyWeights(List weights) { - return new ZInterStoreCommand(getKey(), sourceKeys, weights, aggregateFunction); + public ZUnionStoreCommand applyWeights(List weights) { + return new ZUnionStoreCommand(getKey(), getSourceKeys(), weights, getAggregateFunction().orElse(null)); } /** * Applies the {@link Weights}. Constructs a new command instance with all previously configured properties. * * @param weights must not be {@literal null}. - * @return a new {@link ZInterStoreCommand} with {@literal weights} applied. + * @return a new {@link ZUnionStoreCommand} with {@literal weights} applied. * @since 2.1 */ - public ZInterStoreCommand applyWeights(Weights weights) { + public ZUnionStoreCommand applyWeights(Weights weights) { return applyWeights(weights.toList()); } @@ -1823,11 +2765,11 @@ public ZInterStoreCommand applyWeights(Weights weights) { * properties. * * @param aggregateFunction can be {@literal null}. - * @return a new {@link ZInterStoreCommand} with {@link Aggregate} applied. + * @return a new {@link ZUnionStoreCommand} with {@link Aggregate} applied. */ - public ZInterStoreCommand aggregateUsing(@Nullable Aggregate aggregateFunction) { + public ZUnionStoreCommand aggregateUsing(@Nullable Aggregate aggregateFunction) { - return new ZInterStoreCommand(getKey(), sourceKeys, weights, aggregateFunction); + return new ZUnionStoreCommand(getKey(), getSourceKeys(), getWeights(), aggregateFunction); } /** @@ -1835,65 +2777,45 @@ public ZInterStoreCommand aggregateUsing(@Nullable Aggregate aggregateFunction) * configured properties. * * @param key must not be {@literal null}. - * @return a new {@link ZInterStoreCommand} with {@literal key} applied. + * @return a new {@link ZUnionStoreCommand} with {@literal key} applied. */ - public ZInterStoreCommand storeAs(ByteBuffer key) { + public ZUnionStoreCommand storeAs(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); - return new ZInterStoreCommand(key, sourceKeys, weights, aggregateFunction); - } - - /** - * @return never {@literal null}. - */ - public List getSourceKeys() { - return sourceKeys; - } - - /** - * @return never {@literal null}. - */ - public List getWeights() { - return weights; + return new ZUnionStoreCommand(key, getSourceKeys(), getWeights(), getAggregateFunction().orElse(null)); } - /** - * @return never {@literal null}.ø - */ - public Optional getAggregateFunction() { - return Optional.ofNullable(aggregateFunction); - } } /** - * Intersect sorted {@literal sets} and store result in destination {@literal destinationKey}. + * Union sorted {@literal sets} and store result in destination {@literal destinationKey}. * * @param destinationKey must not be {@literal null}. * @param sets must not be {@literal null}. * @return - * @see Redis Documentation: ZINTERSTORE + * @see Redis Documentation: ZUNIONSTORE */ - default Mono zInterStore(ByteBuffer destinationKey, List sets) { - return zInterStore(destinationKey, sets, Collections.emptyList()); + default Mono zUnionStore(ByteBuffer destinationKey, List sets) { + return zUnionStore(destinationKey, sets, Collections.emptyList()); } /** - * Intersect sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to + * Union sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to * individual sets. * * @param destinationKey must not be {@literal null}. * @param sets must not be {@literal null}. * @param weights must not be {@literal null}. * @return - * @see Redis Documentation: ZINTERSTORE + * @see Redis Documentation: ZUNIONSTORE */ - default Mono zInterStore(ByteBuffer destinationKey, List sets, List weights) { - return zInterStore(destinationKey, sets, weights, null); + default Mono zUnionStore(ByteBuffer destinationKey, List sets, List weights) { + return zUnionStore(destinationKey, sets, weights, null); } /** - * Intersect sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to + * Union sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to * individual sets. * * @param destinationKey must not be {@literal null}. @@ -1901,66 +2823,65 @@ default Mono zInterStore(ByteBuffer destinationKey, List sets, * @param weights must not be {@literal null}. * @return * @since 2.1 - * @see Redis Documentation: ZINTERSTORE + * @see Redis Documentation: ZUNIONSTORE */ - default Mono zInterStore(ByteBuffer destinationKey, List sets, Weights weights) { - return zInterStore(destinationKey, sets, weights, null); + default Mono zUnionStore(ByteBuffer destinationKey, List sets, Weights weights) { + return zUnionStore(destinationKey, sets, weights, null); } /** - * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination + * Union sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination * {@literal destinationKey} and apply weights to individual sets. * * @param destinationKey must not be {@literal null}. * @param sets must not be {@literal null}. - * @param weights must not be {@literal null}. + * @param weights can be {@literal null}. * @param aggregateFunction can be {@literal null}. * @return - * @see Redis Documentation: ZINTERSTORE + * @see Redis Documentation: ZUNIONSTORE */ - default Mono zInterStore(ByteBuffer destinationKey, List sets, List weights, + default Mono zUnionStore(ByteBuffer destinationKey, List sets, List weights, @Nullable Aggregate aggregateFunction) { Assert.notNull(destinationKey, "DestinationKey must not be null!"); Assert.notNull(sets, "Sets must not be null!"); - return zInterStore(Mono.just( - ZInterStoreCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights).storeAs(destinationKey))) - .next().map(NumericResponse::getOutput); + return zUnionStore(Mono.just(ZAggregateStoreCommand.sets(sets).aggregateUsing(aggregateFunction) + .applyWeights(weights).storeAs(destinationKey))).next().map(NumericResponse::getOutput); } /** - * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination + * Union sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination * {@literal destinationKey} and apply weights to individual sets. * * @param destinationKey must not be {@literal null}. * @param sets must not be {@literal null}. - * @param weights must not be {@literal null}. + * @param weights can be {@literal null}. * @param aggregateFunction can be {@literal null}. * @return * @since 2.1 - * @see Redis Documentation: ZINTERSTORE + * @see Redis Documentation: ZUNIONSTORE */ - default Mono zInterStore(ByteBuffer destinationKey, List sets, Weights weights, + default Mono zUnionStore(ByteBuffer destinationKey, List sets, Weights weights, @Nullable Aggregate aggregateFunction) { Assert.notNull(destinationKey, "DestinationKey must not be null!"); Assert.notNull(sets, "Sets must not be null!"); - return zInterStore(Mono.just( - ZInterStoreCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights).storeAs(destinationKey))) + return zUnionStore(Mono.just( + ZUnionStoreCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights).storeAs(destinationKey))) .next().map(NumericResponse::getOutput); } /** - * Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination + * Union sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination * {@literal destinationKey} and apply weights to individual sets. * * @param commands * @return - * @see Redis Documentation: ZINTERSTORE + * @see Redis Documentation: ZUNIONSTORE */ - Flux> zInterStore(Publisher commands); + Flux> zUnionStore(Publisher commands); /** * {@code ZRANGEBYLEX}/{@literal ZREVRANGEBYLEX} command parameters. diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisSetCommands.java index 951499cf70..e9fb5765bb 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 fa8f5ef741..8cb5b9cafa 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java @@ -20,6 +20,7 @@ import java.util.EnumSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.DoubleUnaryOperator; import java.util.function.Function; import java.util.stream.Collectors; @@ -972,6 +973,80 @@ default Long zCount(byte[] key, double min, double max) { @Nullable Long zLexCount(byte[] key, Range range); + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + @Nullable + Tuple zPopMin(byte[] key); + + /** + * Remove and return {@code count} values with their score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + @Nullable + Set zPopMin(byte[] key, long count); + + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout + * @param unit must not be {@literal null}. + * @return can be {@literal null}. + * @see Redis Documentation: BZPOPMIN + * @since 2.6 + */ + @Nullable + Tuple bZPopMin(byte[] key, long timeout, TimeUnit unit); + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + @Nullable + Tuple zPopMax(byte[] key); + + /** + * Remove and return {@code count} values with their score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + @Nullable + Set zPopMax(byte[] key, long count); + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout + * @param unit must not be {@literal null}. + * @return can be {@literal null}. + * @see Redis Documentation: BZPOPMAX + * @since 2.6 + */ + @Nullable + Tuple bZPopMax(byte[] key, long timeout, TimeUnit unit); + /** * Get the size of sorted set with {@code key}. * @@ -993,6 +1068,18 @@ default Long zCount(byte[] key, double min, double max) { @Nullable Double zScore(byte[] key, byte[] value); + /** + * Get the scores of elements with {@code values} from sorted set with key {@code key}. + * + * @param key must not be {@literal null}. + * @param values the values. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZMSCORE + * @since 2.6 + */ + @Nullable + List zMScore(byte[] key, byte[]... values); + /** * Remove elements in range between {@code start} and {@code end} from sorted set with {@code key}. * @@ -1043,47 +1130,91 @@ default Long zRemRangeByScore(byte[] key, double min, double max) { Long zRemRangeByScore(byte[] key, Range range); /** - * 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 {@literal null} when used in pipeline / transaction. - * @see Redis Documentation: ZUNIONSTORE + * @since 2.6 + * @see Redis Documentation: ZDIFF */ @Nullable - Long zUnionStore(byte[] destKey, byte[]... sets); + 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); /** - * Union sorted {@code sets} and store result in destination {@code key}. + * 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 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 {@literal null} when used in pipeline / transaction. - * @see Redis Documentation: ZUNIONSTORE + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER */ @Nullable - default Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) { - return zUnionStore(destKey, aggregate, Weights.of(weights), sets); + default Set zInterWithScores(Aggregate aggregate, int[] weights, byte[]... sets) { + return zInterWithScores(aggregate, Weights.of(weights), sets); } /** - * Union sorted {@code sets} and store result in destination {@code key}. + * Intersect sorted {@code sets}. * - * @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: ZUNIONSTORE + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER */ @Nullable - Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets); + Set zInterWithScores(Aggregate aggregate, Weights weights, byte[]... sets); /** - * Intersect sorted {@code sets} and store result in destination {@code key}. + * 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}. @@ -1094,13 +1225,13 @@ default Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byt Long zInterStore(byte[] destKey, byte[]... sets); /** - * Intersect sorted {@code sets} and store result in destination {@code key}. + * 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 + * @param weights must not be {@literal null}. * @param sets must not be {@literal null}. - * @return + * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: ZINTERSTORE */ @Nullable @@ -1109,19 +1240,110 @@ default Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byt } /** - * Intersect sorted {@code sets} and store result in destination {@code key}. + * 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 + * @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 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 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: ZUNIONSTORE + */ + @Nullable + default Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) { + return zUnionStore(destKey, aggregate, Weights.of(weights), sets); + } + + /** + * 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}. + * @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: ZUNIONSTORE + */ + @Nullable + Long zUnionStore(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 66c9f27936..a8192f1871 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -1411,6 +1411,80 @@ default Long lPos(String key, String element) { @Nullable Long zLexCount(String key, Range range); + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + @Nullable + Tuple zPopMin(String key); + + /** + * Remove and return {@code count} values with their score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + @Nullable + Set zPopMin(String key, long count); + + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout + * @param unit must not be {@literal null}. + * @return can be {@literal null}. + * @see Redis Documentation: BZPOPMIN + * @since 2.6 + */ + @Nullable + StringTuple bZPopMin(String key, long timeout, TimeUnit unit); + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + @Nullable + StringTuple zPopMax(String key); + + /** + * Remove and return {@code count} values with their score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + @Nullable + Set zPopMax(String key, long count); + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout + * @param unit must not be {@literal null}. + * @return can be {@literal null}. + * @see Redis Documentation: BZPOPMAX + * @since 2.6 + */ + @Nullable + StringTuple bZPopMax(String key, long timeout, TimeUnit unit); + /** * Get the size of sorted set with {@code key}. * @@ -1432,6 +1506,18 @@ default Long lPos(String key, String element) { */ Double zScore(String key, String value); + /** + * Get the scores of elements with {@code values} from sorted set with key {@code key}. + * + * @param key must not be {@literal null}. + * @param values the values. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZMSCORE + * @see RedisZSetCommands#zMScore(byte[], byte[][]) + * @since 2.6 + */ + List zMScore(String key, String... values); + /** * Remove elements in range between {@code start} and {@code end} from sorted set with {@code key}. * @@ -1469,28 +1555,88 @@ default Long lPos(String key, String element) { Long zRemRangeByScore(String key, double min, double max); /** - * 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(String... 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(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 must not be {@literal null}. + * @param sets must not be {@literal null}. * @return - * @see Redis Documentation: ZUNIONSTORE - * @see RedisZSetCommands#zUnionStore(byte[], byte[]...) + * @since 2.6 + * @see Redis Documentation: ZINTER */ - Long zUnionStore(String destKey, String... sets); + @Nullable + default Set zInterWithScores(Aggregate aggregate, int[] weights, String... sets) { + return zInterWithScores(aggregate, Weights.of(weights), sets); + } /** - * Union sorted {@code sets} and store result in destination {@code key}. + * Intersect sorted {@code sets}. * - * @param destKey must not be {@literal null}. * @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 + Set zInterWithScores(Aggregate aggregate, Weights weights, String... sets); /** * Intersect sorted {@code sets} and store result in destination {@code key}. @@ -1516,6 +1662,82 @@ default Long lPos(String key, String element) { */ 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 53df378987..eee02bf5a9 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 @@ -18,11 +18,14 @@ import redis.clients.jedis.ScanParams; import redis.clients.jedis.ZParams; +import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.redis.connection.ClusterSlotHashUtil; +import org.springframework.data.redis.connection.DefaultTuple; import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.connection.convert.SetConverter; import org.springframework.data.redis.core.Cursor; @@ -30,6 +33,7 @@ import org.springframework.data.redis.core.ScanIteration; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.util.ByteUtils; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -60,7 +64,8 @@ public Boolean zAdd(byte[] key, double score, byte[] value, ZAddArgs args) { 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); } @@ -280,6 +285,112 @@ public Long zLexCount(byte[] key, Range range) { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMin(byte[]) + */ + @Nullable + @Override + public Tuple zPopMin(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + redis.clients.jedis.Tuple tuple = connection.getCluster().zpopmin(key); + return tuple != null ? JedisConverters.toTuple(tuple) : null; + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMin(byte[], long) + */ + @Nullable + @Override + public Set zPopMin(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + try { + return toTupleSet(connection.getCluster().zpopmin(key, Math.toIntExact(count))); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMin(byte[], long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public Tuple bZPopMin(byte[] key, long timeout, TimeUnit unit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(unit, "TimeUnit must not be null!"); + + try { + return toTuple(connection.getCluster().bzpopmin(JedisConverters.toSeconds(timeout, unit), key)); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(byte[]) + */ + @Nullable + @Override + public Tuple zPopMax(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + redis.clients.jedis.Tuple tuple = connection.getCluster().zpopmax(key); + return tuple != null ? JedisConverters.toTuple(tuple) : null; + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(byte[], long) + */ + @Nullable + @Override + public Set zPopMax(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + try { + return toTupleSet(connection.getCluster().zpopmax(key, Math.toIntExact(count))); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMax(byte[], long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public Tuple bZPopMax(byte[] key, long timeout, TimeUnit unit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(unit, "TimeUnit must not be null!"); + + try { + return toTuple(connection.getCluster().bzpopmax(JedisConverters.toSeconds(timeout, unit), key)); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRangeByScore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range) @@ -637,6 +748,23 @@ public Double zScore(byte[] key, byte[] value) { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zMScore(byte[], byte[][]) + */ + @Override + public List zMScore(byte[] key, byte[][] values) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(values, "Values must not be null!"); + + try { + return connection.getCluster().zmscore(key, values); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRange(byte[], long, long) @@ -671,10 +799,143 @@ public Long zRemRangeByScore(byte[] key, double min, double max) { /* * (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(sets, "Sets must not be null!"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return connection.getCluster().zdiff(sets); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + 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#zDiffWithScores(byte[][]) + */ + @Override + public Set zDiffWithScores(byte[]... sets) { + + Assert.notNull(sets, "Sets must not be null!"); + + if (ClusterSlotHashUtil.isSameSlotForAllKeys(sets)) { + + try { + return JedisConverters.toTupleSet(connection.getCluster().zdiffWithScores(sets)); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + 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"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterStore(byte[], byte[][]) + */ + @Override + public Long zInterStore(byte[] destKey, byte[]... sets) { Assert.notNull(destKey, "Destination key must not be null!"); Assert.notNull(sets, "Source sets must not be null!"); @@ -685,21 +946,21 @@ public Long zUnionStore(byte[] destKey, byte[]... sets) { if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { try { - return connection.getCluster().zunionstore(destKey, sets); + return connection.getCluster().zinterstore(destKey, 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("ZINTERSTORE 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#zInterStore(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) { + public Long zInterStore(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!"); @@ -711,24 +972,89 @@ public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, by if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { - ZParams zparams = new ZParams().weights(weights.toArray()).aggregate(ZParams.Aggregate.valueOf(aggregate.name())); + try { + return connection.getCluster().zinterstore(destKey, toZParams(aggregate, weights), sets); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + + 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().zunionstore(destKey, zparams, sets); + return connection.getCluster().zunion(new ZParams(), 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("ZUNION can only be executed when all keys map to the same slot"); } /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zInterStore(byte[], byte[][]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionWithScores(byte[][]) */ @Override - public Long zInterStore(byte[] destKey, byte[]... sets) { + 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!"); @@ -739,21 +1065,21 @@ public Long zInterStore(byte[] destKey, byte[]... sets) { if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { try { - return connection.getCluster().zinterstore(destKey, sets); + return connection.getCluster().zunionstore(destKey, sets); } catch (Exception ex) { throw convertJedisAccessException(ex); } } - throw new InvalidDataAccessApiUsageException("ZINTERSTORE can only be executed when all keys map to the same slot"); + 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#zInterStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][]) + * @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 zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) { + 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!"); @@ -765,16 +1091,16 @@ public Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, by if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) { - ZParams zparams = new ZParams().weights(weights.toArray()).aggregate(ZParams.Aggregate.valueOf(aggregate.name())); + ZParams zparams = toZParams(aggregate, weights); try { - return connection.getCluster().zinterstore(destKey, zparams, sets); + return connection.getCluster().zunionstore(destKey, zparams, sets); } catch (Exception ex) { throw convertJedisAccessException(ex); } } - throw new IllegalArgumentException("ZINTERSTORE can only be executed when all keys map to the same slot"); + throw new InvalidDataAccessApiUsageException("ZUNIONSTORE can only be executed when all keys map to the same slot"); } /* @@ -846,4 +1172,25 @@ private static Set toTupleSet(Set source) { 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. + * + * @param bytes + * @return + */ + @Nullable + @SuppressWarnings("unchecked") + private static Tuple toTuple(List bytes) { + + if (bytes.isEmpty()) { + return null; + } + + return new DefaultTuple((byte[]) bytes.get(1), Double.parseDouble(new String((byte[]) bytes.get(2)))); + } + } diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java index 1498b3f902..e75ed3ae35 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java @@ -724,6 +724,26 @@ public static GeoRadiusParam toGeoRadiusParam(GeoRadiusCommandArgs source) { return param; } + /** + * Convert a timeout to seconds using {@code double} representation including fraction of seconds. + * + * @param timeout + * @param unit + * @return + * @since 2.6 + */ + static double toSeconds(long timeout, TimeUnit unit) { + + switch (unit) { + case MILLISECONDS: + case MICROSECONDS: + case NANOSECONDS: + return unit.toMillis(timeout) / 1000d; + default: + return unit.toSeconds(timeout); + } + } + /** * Convert given {@link BitFieldSubCommands} into argument array. * 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 2f6b4f67e7..7fb2a4e53d 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 @@ -21,18 +21,20 @@ import redis.clients.jedis.ScanParams; import redis.clients.jedis.ScanResult; import redis.clients.jedis.ZParams; -import redis.clients.jedis.params.ZAddParams; import java.nio.charset.StandardCharsets; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.springframework.data.redis.connection.DefaultTuple; import org.springframework.data.redis.connection.RedisZSetCommands; -import org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs.Flag; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.KeyBoundCursor; import org.springframework.data.redis.core.ScanIteration; import org.springframework.data.redis.core.ScanOptions; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -298,6 +300,96 @@ public Long zLexCount(byte[] key, Range range) { return connection.invoke().just(BinaryJedis::zlexcount, MultiKeyPipelineBase::zlexcount, key, min, max); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMin(byte[]) + */ + @Nullable + @Override + public Tuple zPopMin(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().from(BinaryJedis::zpopmin, MultiKeyPipelineBase::zpopmin, key) + .get(JedisConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMin(byte[], long) + */ + @Nullable + @Override + public Set zPopMin(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke() + .fromMany(BinaryJedis::zpopmin, MultiKeyPipelineBase::zpopmin, key, Math.toIntExact(count)) + .toSet(JedisConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMin(byte[], long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public Tuple bZPopMin(byte[] key, long timeout, TimeUnit unit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(unit, "TimeUnit must not be null!"); + + return connection.invoke() + .from(BinaryJedis::bzpopmin, MultiKeyPipelineBase::bzpopmin, JedisConverters.toSeconds(timeout, unit), key) + .get(JedisZSetCommands::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(byte[]) + */ + @Nullable + @Override + public Tuple zPopMax(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().from(BinaryJedis::zpopmax, MultiKeyPipelineBase::zpopmax, key) + .get(JedisConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(byte[], long) + */ + @Nullable + @Override + public Set zPopMax(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke() + .fromMany(BinaryJedis::zpopmax, MultiKeyPipelineBase::zpopmax, key, Math.toIntExact(count)) + .toSet(JedisConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMax(byte[], long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public Tuple bZPopMax(byte[] key, long timeout, TimeUnit unit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(unit, "TimeUnit must not be null!"); + + return connection.invoke() + .from(BinaryJedis::bzpopmax, MultiKeyPipelineBase::bzpopmax, JedisConverters.toSeconds(timeout, unit), key) + .get(JedisZSetCommands::toTuple); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zCard(byte[]) @@ -323,6 +415,19 @@ public Double zScore(byte[] key, byte[] value) { return connection.invoke().just(BinaryJedis::zscore, MultiKeyPipelineBase::zscore, key, value); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zScore(byte[], byte[][]) + */ + @Override + public List zMScore(byte[] key, byte[][] values) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(values, "Value must not be null!"); + + return connection.invoke().just(BinaryJedis::zmscore, MultiKeyPipelineBase::zmscore, key, values); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRange(byte[], long, long) @@ -371,36 +476,82 @@ public Long zRemRangeByScore(byte[] key, Range range) { /* * (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::zdiff, MultiKeyPipelineBase::zdiff, sets); + } - return connection.invoke().just(BinaryJedis::zunionstore, MultiKeyPipelineBase::zunionstore, destKey, zparams, - sets); + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffWithScores(byte[][]) + */ + @Override + 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#zUnionStore(byte[], byte[][]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffStore(byte[], byte[][]) */ @Override - public Long zUnionStore(byte[] destKey, byte[]... sets) { + 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!"); + + 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().just(BinaryJedis::zunionstore, MultiKeyPipelineBase::zunionstore, destKey, sets); + return connection.invoke().fromMany(BinaryJedis::zinterWithScores, MultiKeyPipelineBase::zinterWithScores, + toZParams(aggregate, weights), sets).toSet(JedisConverters::toTuple); } /* @@ -416,7 +567,7 @@ public Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, by 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); @@ -436,6 +587,82 @@ public Long zInterStore(byte[] destKey, byte[]... sets) { 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) @@ -590,4 +817,25 @@ private boolean isQueueing() { 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. + * + * @param bytes + * @return + */ + @Nullable + @SuppressWarnings("unchecked") + private static Tuple toTuple(List bytes) { + + if (bytes.isEmpty()) { + return null; + } + + return new DefaultTuple((byte[]) bytes.get(1), Double.parseDouble(new String((byte[]) bytes.get(2)))); + } + } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java index edac71ad27..238a8fab70 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java @@ -936,7 +936,7 @@ void transaction(FutureResult result) { RedisClusterAsyncCommands getAsyncConnection() { - if (isQueueing()) { + if (isQueueing() || isPipelined()) { return getAsyncDedicatedConnection(); } @@ -1209,6 +1209,9 @@ static class TypeHints { COMMAND_OUTPUT_TYPE_MAPPING.put(ZINCRBY, DoubleOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(ZSCORE, DoubleOutput.class); + // DOUBLE LIST + COMMAND_OUTPUT_TYPE_MAPPING.put(ZMSCORE, DoubleListOutput.class); + // MAP COMMAND_OUTPUT_TYPE_MAPPING.put(HGETALL, MapOutput.class); @@ -1281,6 +1284,7 @@ static class TypeHints { COMMAND_OUTPUT_TYPE_MAPPING.put(HSET, BooleanOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(HSETNX, BooleanOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(MOVE, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(COPY, BooleanOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(MSETNX, BooleanOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(PERSIST, BooleanOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(PEXPIRE, BooleanOutput.class); diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java index b61cb7a9ae..b413f75fbe 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java @@ -247,7 +247,8 @@ public static Set toTupleSet(@Nullable List> source) } public static Tuple toTuple(@Nullable ScoredValue source) { - return source != null ? new DefaultTuple(source.getValue(), Double.valueOf(source.getScore())) : null; + return source != null && source.hasValue() ? new DefaultTuple(source.getValue(), Double.valueOf(source.getScore())) + : null; } public static String toString(@Nullable byte[] source) { diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterZSetCommands.java index 511ab17665..0d79e7727e 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterZSetCommands.java @@ -41,11 +41,13 @@ class LettuceReactiveClusterZSetCommands extends LettuceReactiveZSetCommands imp super(connection); } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceReactiveZSetCommands#zUnionStore(org.reactivestreams.Publisher) */ @Override - public Flux> zUnionStore(Publisher commands) { + public Flux> zUnionStore( + Publisher commands) { return getConnection().execute(cmd -> Flux.from(commands).concatMap(command -> { @@ -60,11 +62,13 @@ public Flux> zUnionStore(Publisher> zInterStore(Publisher commands) { + public Flux> zInterStore( + Publisher commands) { return getConnection().execute(cmd -> Flux.from(commands).concatMap(command -> { Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty."); diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java index a46f061d07..c6ae8ea222 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java @@ -18,20 +18,24 @@ import io.lettuce.core.Range; import io.lettuce.core.ScanStream; import io.lettuce.core.ScoredValue; +import io.lettuce.core.Value; import io.lettuce.core.ZAddArgs; import io.lettuce.core.ZStoreArgs; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.ByteBuffer; +import java.time.temporal.ChronoUnit; import java.util.List; import org.reactivestreams.Publisher; + import org.springframework.data.domain.Sort.Direction; import org.springframework.data.redis.connection.DefaultTuple; import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse; import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand; import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScanCommand; +import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse; import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse; import org.springframework.data.redis.connection.ReactiveZSetCommands; import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate; @@ -196,22 +200,18 @@ public Flux>> zRange(Publisher new DefaultTuple(getBytes(sc), sc.getScore())); + result = cmd.zrangeWithScores(command.getKey(), start, stop).map(this::toTuple); } else { - result = cmd.zrange(command.getKey(), start, stop) - .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + result = cmd.zrange(command.getKey(), start, stop).map(value -> toTuple(value, Double.NaN)); } } else { if (command.isWithScores()) { - result = cmd.zrevrangeWithScores(command.getKey(), start, stop) - .map(sc -> new DefaultTuple(getBytes(sc), sc.getScore())); + result = cmd.zrevrangeWithScores(command.getKey(), start, stop).map(this::toTuple); } else { - result = cmd.zrevrange(command.getKey(), start, stop) - .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + result = cmd.zrevrange(command.getKey(), start, stop).map(value -> toTuple(value, Double.NaN)); } } @@ -243,22 +243,20 @@ public Flux>> zRangeByScore( if (command.isWithScores()) { if (!isLimited) { - result = cmd.zrangebyscoreWithScores(command.getKey(), range) - .map(sc -> new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); + result = cmd.zrangebyscoreWithScores(command.getKey(), range).map(this::toTuple); } else { result = cmd .zrangebyscoreWithScores(command.getKey(), range, LettuceConverters.toLimit(command.getLimit().get())) - .map(sc -> new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); + .map(this::toTuple); } } else { if (!isLimited) { - result = cmd.zrangebyscore(command.getKey(), range) - .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + result = cmd.zrangebyscore(command.getKey(), range).map(value -> toTuple(value, Double.NaN)); } else { result = cmd.zrangebyscore(command.getKey(), range, LettuceConverters.toLimit(command.getLimit().get())) - .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + .map(value -> toTuple(value, Double.NaN)); } } } else { @@ -268,24 +266,20 @@ public Flux>> zRangeByScore( if (command.isWithScores()) { if (!isLimited) { - result = cmd.zrevrangebyscoreWithScores(command.getKey(), range) - .map(sc -> new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); + result = cmd.zrevrangebyscoreWithScores(command.getKey(), range).map(this::toTuple); } else { - result = cmd - .zrevrangebyscoreWithScores(command.getKey(), range, - LettuceConverters.toLimit(command.getLimit().get())) - .map(sc -> new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); + result = cmd.zrevrangebyscoreWithScores(command.getKey(), range, + LettuceConverters.toLimit(command.getLimit().get())).map(this::toTuple); } } else { if (!isLimited) { - result = cmd.zrevrangebyscore(command.getKey(), range) - .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + result = cmd.zrevrangebyscore(command.getKey(), range).map(value -> toTuple(value, Double.NaN)); } else { result = cmd.zrevrangebyscore(command.getKey(), range, LettuceConverters.toLimit(command.getLimit().get())) - .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + .map(value -> toTuple(value, Double.NaN)); } } } @@ -307,7 +301,7 @@ public Flux>> zScan(Publisher result = ScanStream.zscan(cmd, command.getKey(), LettuceConverters.toScanArgs(command.getOptions())) - .map(it -> new DefaultTuple(ByteUtils.getBytes(it.getValue()), it.getScore())); + .map(this::toTuple); return Mono.just(new CommandResponse<>(command, result)); })); @@ -350,6 +344,52 @@ public Flux> zLexCount(Publisher>> zPop(Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notNull(command.getKey(), "Key must not be null!"); + + Flux> result; + if (command.getCount() > 1) { + result = command.getDirection() == PopDirection.MIN ? cmd.zpopmin(command.getKey(), command.getCount()) + : cmd.zpopmax(command.getKey(), command.getCount()); + } else { + result = (command.getDirection() == PopDirection.MIN ? cmd.zpopmin(command.getKey()) + : cmd.zpopmax(command.getKey())).flux(); + } + + return new CommandResponse<>(command, result.filter(Value::hasValue).map(this::toTuple)); + })); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#bZPop(org.reactivestreams.Publisher) + */ + @Override + public Flux>> bZPop(Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notNull(command.getKey(), "Key must not be null!"); + Assert.notNull(command.getTimeout(), "Timeout must not be null!"); + + long timeout = command.getTimeout().get(ChronoUnit.SECONDS); + + Mono> result = (command.getDirection() == PopDirection.MIN + ? cmd.bzpopmin(timeout, command.getKey()) + : cmd.bzpopmax(timeout, command.getKey())).filter(Value::hasValue).map(Value::getValue); + + return new CommandResponse<>(command, result.filter(Value::hasValue).map(this::toTuple).flux()); + })); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zCard(org.reactivestreams.Publisher) @@ -381,6 +421,23 @@ public Flux> zScore(Publisher> zMScore(Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).concatMap(command -> { + + Assert.notNull(command.getKey(), "Key must not be null!"); + Assert.notNull(command.getValues(), "Values must not be null!"); + + return cmd.zmscore(command.getKey(), command.getValues().toArray(new ByteBuffer[0])) + .map(value -> new MultiValueResponse<>(command, value)); + })); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRemRangeByRank(org.reactivestreams.Publisher) @@ -442,26 +499,99 @@ public Flux> zRemRangeByLex(Publish /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zUnionStore(org.reactivestreams.Publisher) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zDiff(Publisher) + */ + @Override + public Flux>> zDiff(Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notEmpty(command.getKeys(), "Keys must not be null or empty!"); + + ByteBuffer[] sourceKeys = command.getKeys().toArray(new ByteBuffer[0]); + return new CommandResponse<>(command, cmd.zdiff(sourceKeys)); + })); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zDiffWithScores(Publisher) */ @Override - public Flux> zUnionStore(Publisher commands) { + public Flux>> zDiffWithScores(Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notEmpty(command.getKeys(), "Keys must not be null or empty!"); + + ByteBuffer[] sourceKeys = command.getKeys().toArray(new ByteBuffer[0]); + return new CommandResponse<>(command, cmd.zdiffWithScores(sourceKeys).map(this::toTuple)); + })); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zDiffStore(Publisher) + */ + @Override + public Flux> zDiffStore(Publisher commands) { return connection.execute(cmd -> Flux.from(commands).concatMap(command -> { Assert.notNull(command.getKey(), "Destination key must not be null!"); Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty!"); + ByteBuffer[] sourceKeys = command.getSourceKeys().toArray(new ByteBuffer[0]); + return cmd.zdiffstore(command.getKey(), sourceKeys).map(value -> new NumericResponse<>(command, value)); + })); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zInter(Publisher) + */ + @Override + public Flux>> zInter( + Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty!"); + ZStoreArgs args = null; if (command.getAggregateFunction().isPresent() || !command.getWeights().isEmpty()) { args = zStoreArgs(command.getAggregateFunction().isPresent() ? command.getAggregateFunction().get() : null, command.getWeights()); } - ByteBuffer[] sourceKeys = command.getSourceKeys().stream().toArray(ByteBuffer[]::new); - Mono result = args != null ? cmd.zunionstore(command.getKey(), args, sourceKeys) - : cmd.zunionstore(command.getKey(), sourceKeys); - return result.map(value -> new NumericResponse<>(command, value)); + ByteBuffer[] sourceKeys = command.getSourceKeys().toArray(new ByteBuffer[0]); + Flux result = args != null ? cmd.zinter(args, sourceKeys) : cmd.zinter(sourceKeys); + return new CommandResponse<>(command, result); + })); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zInterWithScores(Publisher) + */ + @Override + public Flux>> zInterWithScores( + Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty!"); + + ZStoreArgs args = null; + if (command.getAggregateFunction().isPresent() || !command.getWeights().isEmpty()) { + args = zStoreArgs(command.getAggregateFunction().isPresent() ? command.getAggregateFunction().get() : null, + command.getWeights()); + } + + ByteBuffer[] sourceKeys = command.getSourceKeys().toArray(new ByteBuffer[0]); + Flux> result = args != null ? cmd.zinterWithScores(args, sourceKeys) + : cmd.zinterWithScores(sourceKeys); + return new CommandResponse<>(command, result.map(this::toTuple)); })); } @@ -470,7 +600,8 @@ public Flux> zUnionStore(Publisher> zInterStore(Publisher commands) { + public Flux> zInterStore( + Publisher commands) { return connection.execute(cmd -> Flux.from(commands).concatMap(command -> { @@ -483,13 +614,88 @@ public Flux> zInterStore(Publisher result = args != null ? cmd.zinterstore(command.getKey(), args, sourceKeys) : cmd.zinterstore(command.getKey(), sourceKeys); return result.map(value -> new NumericResponse<>(command, value)); })); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zUnion(org.reactivestreams.Publisher) + */ + @Override + public Flux>> zUnion( + Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty!"); + + ZStoreArgs args = null; + if (command.getAggregateFunction().isPresent() || !command.getWeights().isEmpty()) { + args = zStoreArgs(command.getAggregateFunction().isPresent() ? command.getAggregateFunction().get() : null, + command.getWeights()); + } + + ByteBuffer[] sourceKeys = command.getSourceKeys().stream().toArray(ByteBuffer[]::new); + Flux result = args != null ? cmd.zunion(args, sourceKeys) : cmd.zunion(sourceKeys); + return new CommandResponse<>(command, result); + })); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zUnion(org.reactivestreams.Publisher) + */ + @Override + public Flux>> zUnionWithScores( + Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).map(command -> { + + Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty!"); + + ZStoreArgs args = null; + if (command.getAggregateFunction().isPresent() || !command.getWeights().isEmpty()) { + args = zStoreArgs(command.getAggregateFunction().isPresent() ? command.getAggregateFunction().get() : null, + command.getWeights()); + } + + ByteBuffer[] sourceKeys = command.getSourceKeys().stream().toArray(ByteBuffer[]::new); + Flux> result = args != null ? cmd.zunionWithScores(args, sourceKeys) + : cmd.zunionWithScores(sourceKeys); + return new CommandResponse<>(command, result.map(this::toTuple)); + })); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zUnionStore(org.reactivestreams.Publisher) + */ + @Override + public Flux> zUnionStore( + Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).concatMap(command -> { + + Assert.notNull(command.getKey(), "Destination key must not be null!"); + Assert.notEmpty(command.getSourceKeys(), "Source keys must not be null or empty!"); + + ZStoreArgs args = null; + if (command.getAggregateFunction().isPresent() || !command.getWeights().isEmpty()) { + args = zStoreArgs(command.getAggregateFunction().isPresent() ? command.getAggregateFunction().get() : null, + command.getWeights()); + } + + ByteBuffer[] sourceKeys = command.getSourceKeys().stream().toArray(ByteBuffer[]::new); + Mono result = args != null ? cmd.zunionstore(command.getKey(), args, sourceKeys) + : cmd.zunionstore(command.getKey(), sourceKeys); + return result.map(value -> new NumericResponse<>(command, value)); + })); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRangeByLex(org.reactivestreams.Publisher) @@ -549,8 +755,12 @@ private static ZStoreArgs zStoreArgs(@Nullable Aggregate aggregate, @Nullable Li return args; } - private static byte[] getBytes(ScoredValue scoredValue) { - return scoredValue.optional().map(ByteUtils::getBytes).orElse(new byte[0]); + private Tuple toTuple(ScoredValue scoredValue) { + return scoredValue.map(it -> new DefaultTuple(ByteUtils.getBytes(it), scoredValue.getScore())).getValue(); + } + + private Tuple toTuple(ByteBuffer value, double score) { + return new DefaultTuple(ByteUtils.getBytes(value), score); } protected LettuceReactiveRedisConnection getConnection() { 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 7376860d81..cb730ce2dc 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,12 +18,14 @@ 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; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs.Flag; @@ -277,6 +279,92 @@ public Long zLexCount(byte[] key, Range range) { LettuceConverters. toRange(range, true)); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMin(byte[]) + */ + @Nullable + @Override + public Tuple zPopMin(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().from(RedisSortedSetAsyncCommands::zpopmin, key).get(LettuceConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMin(byte[], long) + */ + @Nullable + @Override + public Set zPopMin(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zpopmin, key, count) + .toSet(LettuceConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMin(byte[], long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public Tuple bZPopMin(byte[] key, long timeout, TimeUnit unit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(unit, "TimeUnit must not be null!"); + + return connection.invoke(connection.getAsyncDedicatedConnection()) + .from(RedisSortedSetAsyncCommands::bzpopmin, unit.toSeconds(timeout), key) + .get(it -> it.map(LettuceConverters::toTuple).getValueOrElse(null)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(byte[]) + */ + @Nullable + @Override + public Tuple zPopMax(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().from(RedisSortedSetAsyncCommands::zpopmax, key).get(LettuceConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zPopMax(byte[], long) + */ + @Nullable + @Override + public Set zPopMax(byte[] key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zpopmax, key, count) + .toSet(LettuceConverters::toTuple); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#bZPopMax(byte[], long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public Tuple bZPopMax(byte[] key, long timeout, TimeUnit unit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(unit, "TimeUnit must not be null!"); + + return connection.invoke(connection.getAsyncDedicatedConnection()) + .from(RedisSortedSetAsyncCommands::bzpopmax, unit.toSeconds(timeout), key) + .get(it -> it.map(LettuceConverters::toTuple).getValueOrElse(null)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zCard(byte[]) @@ -302,6 +390,19 @@ public Double zScore(byte[] key, byte[] value) { return connection.invoke().just(RedisSortedSetAsyncCommands::zscore, key, value); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zMScore(byte[], byte[][]) + */ + @Override + public List zMScore(byte[] key, byte[][] values) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(values, "Value must not be null!"); + + return connection.invoke().just(RedisSortedSetAsyncCommands::zmscore, key, values); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRange(byte[], long, long) @@ -344,34 +445,83 @@ public Long zRemRangeByScore(byte[] key, Range range) { /* * (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().fromMany(RedisSortedSetAsyncCommands::zdiff, sets).toSet(); + } - return connection.invoke().just(RedisSortedSetAsyncCommands::zunionstore, destKey, storeArgs, sets); + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffWithScores(byte[][]) + */ + @Override + 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#zUnionStore(byte[], byte[][]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zDiffStore(byte[], byte[][]) */ @Override - public Long zUnionStore(byte[] destKey, byte[]... sets) { + 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!"); + + 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)); - return connection.invoke().just(RedisSortedSetAsyncCommands::zunionstore, destKey, sets); + ZAggregateArgs zAggregateArgs = zAggregateArgs(aggregate, weights); + + return connection.invoke().fromMany(RedisSortedSetAsyncCommands::zinterWithScores, zAggregateArgs, sets) + .toSet(LettuceConverters::toTuple); } /* @@ -406,6 +556,81 @@ public Long zInterStore(byte[] destKey, byte[]... sets) { 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) @@ -570,6 +795,29 @@ private static ZStoreArgs zStoreArgs(@Nullable Aggregate aggregate, Weights weig 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/AbstractOperations.java b/src/main/java/org/springframework/data/redis/core/AbstractOperations.java index 53048d9b4a..4cd8a942dc 100644 --- a/src/main/java/org/springframework/data/redis/core/AbstractOperations.java +++ b/src/main/java/org/springframework/data/redis/core/AbstractOperations.java @@ -223,7 +223,8 @@ Set deserializeValues(Set rawValues) { return SerializationUtils.deserialize(rawValues, valueSerializer()); } - Set> deserializeTupleValues(Collection rawValues) { + @Nullable + Set> deserializeTupleValues(@Nullable Collection rawValues) { if (rawValues == null) { return null; } @@ -235,7 +236,11 @@ Set> deserializeTupleValues(Collection rawValues) { } @SuppressWarnings({ "unchecked", "rawtypes" }) - TypedTuple deserializeTuple(Tuple tuple) { + @Nullable + TypedTuple deserializeTuple(@Nullable Tuple tuple) { + if (tuple == null) { + return null; + } Object value = tuple.getValue(); if (valueSerializer() != null) { value = valueSerializer().deserialize(tuple.getValue()); 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 8ff3df2eee..94ffe72ced 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java @@ -15,8 +15,12 @@ */ 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; import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate; import org.springframework.data.redis.connection.RedisZSetCommands.Limit; @@ -25,6 +29,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Weights; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * ZSet (or SortedSet) operations bound to a certain key. @@ -49,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. @@ -71,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. @@ -234,6 +239,112 @@ public interface BoundZSetOperations extends BoundKeyOperations { @Nullable Long lexCount(Range range); + /** + * Remove and return the value with its score having the lowest score from sorted set at the bound key. + * + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + @Nullable + TypedTuple popMin(); + + /** + * Remove and return {@code count} values with their score having the lowest score from sorted set at the bound key. + * + * @param count number of elements to pop. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + @Nullable + Set> popMin(long count); + + /** + * Remove and return the value with its score having the lowest score from sorted set at the bound key. Blocks + * connection until element available or {@code timeout} reached. + * + * @param timeout + * @param unit must not be {@literal null}. + * @return can be {@literal null}. + * @see Redis Documentation: BZPOPMIN + * @since 2.6 + */ + @Nullable + TypedTuple popMin(long timeout, TimeUnit unit); + + /** + * Remove and return the value with its score having the lowest score from sorted set at the bound key. Blocks + * connection until element available or {@code timeout} reached. + * + * @param timeout must not be {@literal null}. + * @return can be {@literal null}. + * @throws IllegalArgumentException if the timeout is {@literal null} or negative. + * @see Redis Documentation: BZPOPMIN + * @since 2.6 + */ + @Nullable + default TypedTuple popMin(Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null"); + Assert.isTrue(!timeout.isNegative(), "Timeout must not be negative"); + + return popMin(TimeoutUtils.toSeconds(timeout), TimeUnit.SECONDS); + } + + /** + * Remove and return the value with its score having the highest score from sorted set at the bound key. + * + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + @Nullable + TypedTuple popMax(); + + /** + * Remove and return {@code count} values with their score having the highest score from sorted set at the bound key. + * + * @param count number of elements to pop. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + @Nullable + Set> popMax(long count); + + /** + * Remove and return the value with its score having the highest score from sorted set at the bound key. Blocks + * connection until element available or {@code timeout} reached. + * + * @param timeout + * @param unit must not be {@literal null}. + * @return can be {@literal null}. + * @see Redis Documentation: BZPOPMAX + * @since 2.6 + */ + @Nullable + TypedTuple popMax(long timeout, TimeUnit unit); + + /** + * Remove and return the value with its score having the highest score from sorted set at the bound key. Blocks + * connection until element available or {@code timeout} reached. + * + * @param timeout must not be {@literal null}. + * @return can be {@literal null}. + * @throws IllegalArgumentException if the timeout is {@literal null} or negative. + * @see Redis Documentation: BZPOPMAX + * @since 2.6 + */ + @Nullable + default TypedTuple popMax(Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null"); + Assert.isTrue(!timeout.isNegative(), "Timeout must not be negative"); + + return popMax(TimeoutUtils.toSeconds(timeout), TimeUnit.SECONDS); + } + /** * Returns the number of elements of the sorted set stored with given the bound key. * @@ -264,6 +375,17 @@ public interface BoundZSetOperations extends BoundKeyOperations { @Nullable Double score(Object o); + /** + * Get the scores of elements with {@code values} from sorted set with key the bound key. + * + * @param o the values. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZMSCORE + * @since 2.6 + */ + @Nullable + List score(Object... o); + /** * Remove elements in range between {@code start} and {@code end} from sorted set with the bound key. * @@ -300,51 +422,151 @@ public interface BoundZSetOperations extends BoundKeyOperations { /** * 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 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 otherKey, K destKey); + Long unionAndStore(Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); /** - * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. + * 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}. - * @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(Collection otherKeys, K destKey); + Set difference(Collection otherKeys); /** - * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. + * 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}. - * @param aggregate 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(Collection otherKeys, K destKey, Aggregate aggregate); + default Long differenceAndStore(K otherKey, K destKey) { + return differenceAndStore(Collections.singleton(otherKey), destKey); + } /** - * Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}. + * 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 {@literal null} when used in pipeline / transaction. - * @since 2.1 - * @see Redis Documentation: ZUNIONSTORE + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER */ @Nullable - Long unionAndStore(Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); + 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}. @@ -395,6 +617,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 f9a4a42bee..a12c1287a7 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java @@ -17,7 +17,9 @@ package org.springframework.data.redis.core; import java.util.Collection; +import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate; @@ -25,6 +27,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Range; import org.springframework.data.redis.connection.RedisZSetCommands.Weights; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import org.springframework.lang.Nullable; /** * Default implementation for {@link BoundZSetOperations}. @@ -105,42 +108,6 @@ public RedisOperations getOperations() { 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) @@ -249,6 +216,15 @@ public Double score(Object o) { return ops.score(getKey(), o); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#score(java.lang.Object[]) + */ + @Override + public List score(Object... o) { + return ops.score(getKey(), o); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundZSetOperations#remove(java.lang.Object[]) @@ -312,6 +288,66 @@ public Long lexCount(Range range) { return ops.lexCount(getKey(), range); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#popMin() + */ + @Nullable + @Override + public TypedTuple popMin() { + return ops.popMin(getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#popMin(long) + */ + @Nullable + @Override + public Set> popMin(long count) { + return ops.popMin(getKey(), count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#popMin(long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public TypedTuple popMin(long timeout, TimeUnit unit) { + return ops.popMin(getKey(), timeout, unit); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#popMax() + */ + @Nullable + @Override + public TypedTuple popMax() { + return ops.popMax(getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#popMax(long) + */ + @Nullable + @Override + public Set> popMax(long count) { + return ops.popMax(getKey(), count); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#popMax(long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public TypedTuple popMax(long timeout, TimeUnit unit) { + return ops.popMax(getKey(), timeout, unit); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundZSetOperations#size() @@ -330,6 +366,132 @@ public Long zCard() { 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/DefaultReactiveZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java index 0995bc96d0..5d018caa44 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java @@ -19,6 +19,7 @@ import reactor.core.publisher.Mono; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -345,6 +346,80 @@ public Mono lexCount(K key, Range range) { return createMono(connection -> connection.zLexCount(rawKey(key), range)); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#popMin(java.lang.Object) + */ + @Override + public Mono> popMin(K key) { + + Assert.notNull(key, "Key must not be null!"); + + return createMono(connection -> connection.zPopMin(rawKey(key)).map(this::readTypedTuple)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#popMin(java.lang.Object, long) + */ + @Override + public Flux> popMin(K key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return createFlux(connection -> connection.zPopMin(rawKey(key), count).map(this::readTypedTuple)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#popMin(java.lang.Object, java.time.Duration) + */ + @Override + public Mono> popMin(K key, Duration timeout) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(timeout, "Timeout must not be null!"); + + return createMono(connection -> connection.bZPopMin(rawKey(key), timeout).map(this::readTypedTuple)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#popMax(java.lang.Object) + */ + @Override + public Mono> popMax(K key) { + + Assert.notNull(key, "Key must not be null!"); + + return createMono(connection -> connection.zPopMax(rawKey(key)).map(this::readTypedTuple)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#popMax(java.lang.Object, long) + */ + @Override + public Flux> popMax(K key, long count) { + + Assert.notNull(key, "Key must not be null!"); + + return createFlux(connection -> connection.zPopMax(rawKey(key), count).map(this::readTypedTuple)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#popMax(java.lang.Object, java.time.Duration) + */ + @Override + public Mono> popMax(K key, Duration timeout) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(timeout, "Timeout must not be null!"); + + return createMono(connection -> connection.bZPopMax(rawKey(key), timeout).map(this::readTypedTuple)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveZSetOperations#size(java.lang.Object) @@ -370,6 +445,22 @@ public Mono score(K key, Object o) { return createMono(connection -> connection.zScore(rawKey(key), rawValue((V) o))); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#score(java.lang.Object, java.lang.Object) + */ + @Override + @SuppressWarnings("unchecked") + public Mono> score(K key, Object... o) { + + Assert.notNull(key, "Key must not be null!"); + + return createMono(connection -> Flux.fromArray((V[]) o) // + .map(this::rawValue) // + .collectList() // + .flatMap(values -> connection.zMScore(rawKey(key), values))); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveZSetOperations#removeRange(java.lang.Object, org.springframework.data.domain.Range) @@ -409,26 +500,114 @@ public Mono removeRangeByScore(K key, Range range) { return createMono(connection -> connection.zRemRangeByScore(rawKey(key), range)); } - /* + /* * (non-Javadoc) - * @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(java.lang.Object, java.lang.Object, java.lang.Object) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#difference(K, Collection) */ @Override - public Mono unionAndStore(K key, K otherKey, K destKey) { + public Flux difference(K key, Collection otherKeys) { Assert.notNull(key, "Key must not be null!"); - Assert.notNull(otherKey, "Other key must not be null!"); + Assert.notNull(otherKeys, "Other keys must not be null!"); + + return createFlux(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(connection::zDiff).map(this::readValue)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#differenceWithScores(K, Collection) + */ + @Override + public Flux> differenceWithScores(K key, Collection otherKeys) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(otherKeys, "Other keys must not be null!"); + + return createFlux(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(connection::zDiffWithScores).map(this::readTypedTuple)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#differenceAndStore(K, Collection, K) + */ + @Override + public Mono differenceAndStore(K key, Collection otherKeys, K destKey) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(otherKeys, "Other keys must not be null!"); Assert.notNull(destKey, "Destination key must not be null!"); - return unionAndStore(key, Collections.singleton(otherKey), destKey); + return createMono(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMap(serialized -> connection.zDiffStore(rawKey(destKey), serialized))); + + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#intersect(K, Collection) + */ + @Override + public Flux intersect(K key, Collection otherKeys) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(otherKeys, "Other keys must not be null!"); + + return createFlux(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(connection::zInter).map(this::readValue)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectWithScores(K, Collection) + */ + @Override + public Flux> intersectWithScores(K key, Collection otherKeys) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(otherKeys, "Other keys must not be null!"); + + return createFlux(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(connection::zInterWithScores).map(this::readTypedTuple)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectWithScores(K, Collection, Aggregate, Weights) + */ + @Override + public Flux> intersectWithScores(K key, Collection otherKeys, Aggregate aggregate, Weights weights) { + + // TODO: Inconsistent method signatures Aggregate/Weights vs Weights/Aggregate in Connection API + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(otherKeys, "Other keys must not be null!"); + Assert.notNull(aggregate, "Aggregate must not be null!"); + Assert.notNull(weights, "Weights must not be null!"); + + return createFlux(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(sets -> connection.zInterWithScores(sets, weights, aggregate)).map(this::readTypedTuple)); } /* * (non-Javadoc) - * @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(java.lang.Object, java.util.Collection, java.lang.Object) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object) */ @Override - public Mono unionAndStore(K key, Collection otherKeys, K destKey) { + public Mono intersectAndStore(K key, Collection otherKeys, K destKey) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(otherKeys, "Other keys must not be null!"); @@ -437,15 +616,15 @@ public Mono unionAndStore(K key, Collection otherKeys, K destKey) { return createMono(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // .map(this::rawKey) // .collectList() // - .flatMap(serialized -> connection.zUnionStore(rawKey(destKey), serialized))); + .flatMap(serialized -> connection.zInterStore(rawKey(destKey), serialized))); } /* * (non-Javadoc) - * @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#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 Mono unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { + public Mono intersectAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(otherKeys, "Other keys must not be null!"); @@ -456,29 +635,79 @@ public Mono unionAndStore(K key, Collection otherKeys, K destKey, Aggre return createMono(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // .map(this::rawKey) // .collectList() // - .flatMap(serialized -> connection.zUnionStore(rawKey(destKey), serialized, weights, aggregate))); + .flatMap(serialized -> connection.zInterStore(rawKey(destKey), serialized, weights, aggregate))); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#union(K, Collection) + */ + @Override + public Flux union(K key, Collection otherKeys) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(otherKeys, "Other keys must not be null!"); + + return createFlux(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(connection::zUnion).map(this::readValue)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#unionWithScores(K, Collection) + */ + @Override + public Flux> unionWithScores(K key, Collection otherKeys) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(otherKeys, "Other keys must not be null!"); + + return createFlux(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(connection::zUnionWithScores).map(this::readTypedTuple)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#unionWithScores(K, Collection, Aggregate, Weights) + */ + @Override + public Flux> unionWithScores(K key, Collection otherKeys, Aggregate aggregate, Weights weights) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(otherKeys, "Other keys must not be null!"); + Assert.notNull(aggregate, "Aggregate must not be null!"); + Assert.notNull(weights, "Weights must not be null!"); + + return createFlux(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(sets -> connection.zUnionWithScores(sets, weights, aggregate)).map(this::readTypedTuple)); } /* * (non-Javadoc) - * @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectAndStore(java.lang.Object, java.lang.Object, java.lang.Object) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(java.lang.Object, java.lang.Object, java.lang.Object) */ @Override - public Mono intersectAndStore(K key, K otherKey, K destKey) { + public Mono unionAndStore(K key, K otherKey, K destKey) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(otherKey, "Other key must not be null!"); Assert.notNull(destKey, "Destination key must not be null!"); - return intersectAndStore(key, Collections.singleton(otherKey), destKey); + return unionAndStore(key, Collections.singleton(otherKey), destKey); } /* * (non-Javadoc) - * @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(java.lang.Object, java.util.Collection, java.lang.Object) */ @Override - public Mono intersectAndStore(K key, Collection otherKeys, K destKey) { + public Mono unionAndStore(K key, Collection otherKeys, K destKey) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(otherKeys, "Other keys must not be null!"); @@ -487,15 +716,15 @@ public Mono intersectAndStore(K key, Collection otherKeys, K destKey) { return createMono(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // .map(this::rawKey) // .collectList() // - .flatMap(serialized -> connection.zInterStore(rawKey(destKey), serialized))); + .flatMap(serialized -> connection.zUnionStore(rawKey(destKey), serialized))); } /* * (non-Javadoc) - * @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(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 Mono intersectAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { + public Mono unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(otherKeys, "Other keys must not be null!"); @@ -506,7 +735,7 @@ public Mono intersectAndStore(K key, Collection otherKeys, K destKey, A return createMono(connection -> Flux.fromIterable(getKeys(key, otherKeys)) // .map(this::rawKey) // .collectList() // - .flatMap(serialized -> connection.zInterStore(rawKey(destKey), serialized, weights, aggregate))); + .flatMap(serialized -> connection.zUnionStore(rawKey(destKey), serialized, weights, aggregate))); } /* diff --git a/src/main/java/org/springframework/data/redis/core/DefaultTypedTuple.java b/src/main/java/org/springframework/data/redis/core/DefaultTypedTuple.java index bf3ecda2c1..4bf8a9778d 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultTypedTuple.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultTypedTuple.java @@ -102,4 +102,14 @@ public int compareTo(TypedTuple o) { return compareTo(o.getScore()); } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append(" [score=").append(score); + sb.append(", value=").append(value); + sb.append(']'); + return sb.toString(); + } } 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 79ae492d80..a1e965d9de 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java @@ -17,7 +17,9 @@ import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate; import org.springframework.data.redis.connection.RedisZSetCommands.Limit; @@ -128,41 +130,6 @@ public Double incrementScore(K key, V value, double delta) { 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) @@ -437,6 +404,18 @@ public Double score(K key, Object o) { return execute(connection -> connection.zScore(rawKey, rawValue), true); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#score(java.lang.Object, java.lang.Object[]) + */ + @Override + public List score(K key, Object... o) { + + byte[] rawKey = rawKey(key); + byte[][] rawValues = rawValues(o); + return execute(connection -> connection.zMScore(rawKey, rawValues), true); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ZSetOperations#count(java.lang.Object, double, double) @@ -459,6 +438,78 @@ public Long lexCount(K key, Range range) { return execute(connection -> connection.zLexCount(rawKey, range), true); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#popMin(java.lang.Object) + */ + @Nullable + @Override + public TypedTuple popMin(K key) { + + byte[] rawKey = rawKey(key); + return deserializeTuple(execute(connection -> connection.zPopMin(rawKey), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#popMin(java.lang.Object, long) + */ + @Nullable + @Override + public Set> popMin(K key, long count) { + + byte[] rawKey = rawKey(key); + return deserializeTupleValues(execute(connection -> connection.zPopMin(rawKey, count), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#popMin(java.lang.Object, long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public TypedTuple popMin(K key, long timeout, TimeUnit unit) { + + byte[] rawKey = rawKey(key); + return deserializeTuple(execute(connection -> connection.bZPopMin(rawKey, timeout, unit), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#popMax(java.lang.Object) + */ + @Nullable + @Override + public TypedTuple popMax(K key) { + + byte[] rawKey = rawKey(key); + return deserializeTuple(execute(connection -> connection.zPopMax(rawKey), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#popMax(java.lang.Object, long) + */ + @Nullable + @Override + public Set> popMax(K key, long count) { + + byte[] rawKey = rawKey(key); + return deserializeTupleValues(execute(connection -> connection.zPopMax(rawKey, count), true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#popMax(java.lang.Object, long, java.util.concurrent.TimeUnit) + */ + @Nullable + @Override + public TypedTuple popMax(K key, long timeout, TimeUnit unit) { + + byte[] rawKey = rawKey(key); + return deserializeTuple(execute(connection -> connection.bZPopMax(rawKey, timeout, unit), true)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ZSetOperations#size(java.lang.Object) @@ -479,6 +530,147 @@ public Long zCard(K key) { 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/ReactiveZSetOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java index 1914da9664..0e4565754d 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java @@ -18,7 +18,10 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.time.Duration; import java.util.Collection; +import java.util.Collections; +import java.util.List; import org.springframework.data.domain.Range; import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate; @@ -281,6 +284,74 @@ default Flux> scan(K key) { */ Mono lexCount(K key, Range range); + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + Mono> popMin(K key); + + /** + * Remove and return {@code count} values with their score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + Flux> popMin(K key, long count); + + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param timeout maximal duration to wait until an entry in the list at {@code key} is available. Must be either + * {@link Duration#ZERO} or greater {@link 1 second}, must not be {@literal null}. A timeout of zero can be + * used to wait indefinitely. Durations between zero and one second are not supported. + * @return + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + Mono> popMin(K key, Duration timeout); + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + Mono> popMax(K key); + + /** + * Remove and return {@code count} values with their score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + Flux> popMax(K key, long count); + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param timeout maximal duration to wait until an entry in the list at {@code key} is available. Must be either + * {@link Duration#ZERO} or greater {@link 1 second}, must not be {@literal null}. A timeout of zero can be + * used to wait indefinitely. Durations between zero and one second are not supported. + * @return + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + Mono> popMax(K key, Duration timeout); + /** * Returns the number of elements of the sorted set stored with given {@code key}. * @@ -300,6 +371,17 @@ default Flux> scan(K key) { */ Mono score(K key, Object o); + /** + * Get the scores of elements with {@code values} from sorted set with key {@code key}. + * + * @param key must not be {@literal null}. + * @param o the values. + * @return + * @see Redis Documentation: ZMSCORE + * @since 2.6 + */ + Mono> score(K key, Object... o); + /** * Remove elements in range between {@code start} and {@code end} from sorted set with {@code key}. * @@ -332,55 +414,153 @@ default Flux> scan(K key) { Mono removeRangeByScore(K key, Range range); /** - * 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 - * @see Redis Documentation: ZUNIONSTORE + * @since 2.6 + * @see Redis Documentation: ZDIFF */ - Mono unionAndStore(K key, K otherKey, K destKey); + default Flux 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 + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + Flux difference(K key, Collection otherKeys); + + /** + * Diff sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + default Flux> 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 + * @since 2.6 + * @see Redis Documentation: ZDIFF + */ + Flux> 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}. * @return - * @see Redis Documentation: ZUNIONSTORE + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE */ - Mono unionAndStore(K key, Collection otherKeys, K destKey); + default Mono differenceAndStore(K key, K otherKey, K destKey) { + return differenceAndStore(key, Collections.singleton(otherKey), destKey); + } /** - * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. + * 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}. + * @return + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE + */ + Mono 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 + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + default Flux 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 + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + Flux intersect(K key, Collection otherKeys); + + /** + * Intersect sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + default Flux> 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 + * @since 2.6 + * @see Redis Documentation: ZINTER + */ + Flux> 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 - * @since 2.1 - * @see Redis Documentation: ZUNIONSTORE + * @since 2.6 + * @see Redis Documentation: ZINTER */ - default Mono unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate) { - return unionAndStore(key, otherKeys, destKey, aggregate, Weights.fromSetCount(1 + otherKeys.size())); + default Flux> intersectWithScores(K key, Collection otherKeys, Aggregate aggregate) { + return intersectWithScores(key, otherKeys, aggregate, Weights.fromSetCount(1 + otherKeys.size())); } /** - * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. + * Intersect sorted {@code sets}. * * @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 - * @since 2.1 - * @see Redis Documentation: ZUNIONSTORE + * @since 2.6 + * @see Redis Documentation: ZINTER */ - Mono unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); + Flux> 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}. @@ -391,7 +571,9 @@ default Mono unionAndStore(K key, Collection otherKeys, K destKey, Aggr * @return * @see Redis Documentation: ZINTERSTORE */ - Mono intersectAndStore(K key, K otherKey, K destKey); + default Mono intersectAndStore(K key, K otherKey, K destKey) { + return intersectAndStore(key, Collections.singleton(otherKey), destKey); + } /** * Intersect sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. @@ -433,6 +615,132 @@ default Mono intersectAndStore(K key, Collection otherKeys, K destKey, */ Mono 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 + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + default Flux 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 + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + Flux union(K key, Collection otherKeys); + + /** + * Union sorted {@code sets}. + * + * @param key must not be {@literal null}. + * @param otherKey must not be {@literal null}. + * @return + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + default Flux> 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 + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + Flux> 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 + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + default Flux> 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 + * @since 2.6 + * @see Redis Documentation: ZUNION + */ + Flux> 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 + * @see Redis Documentation: ZUNIONSTORE + */ + Mono 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 + * @see Redis Documentation: ZUNIONSTORE + */ + Mono 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 + * @since 2.1 + * @see Redis Documentation: ZUNIONSTORE + */ + default Mono 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 + * @since 2.1 + * @see Redis Documentation: ZUNIONSTORE + */ + Mono unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); + /** * Get all elements with lexicographical ordering from {@literal ZSET} at {@code key} with a value between * {@link Range#getLowerBound()} and {@link Range#getUpperBound()}. 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 822b8a47a7..99493dffe3 100644 --- a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java @@ -15,8 +15,12 @@ */ 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; import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate; import org.springframework.data.redis.connection.RedisZSetCommands.Limit; @@ -24,6 +28,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; import org.springframework.data.redis.connection.RedisZSetCommands.Weights; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Redis ZSet/sorted set specific operations. @@ -337,6 +342,120 @@ static TypedTuple of(V value, @Nullable Double score) { @Nullable Long lexCount(K key, Range range); + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + @Nullable + TypedTuple popMin(K key); + + /** + * Remove and return {@code count} values with their score having the lowest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMIN + * @since 2.6 + */ + @Nullable + Set> popMin(K key, long count); + + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout + * @param unit must not be {@literal null}. + * @return can be {@literal null}. + * @see Redis Documentation: BZPOPMIN + * @since 2.6 + */ + @Nullable + TypedTuple popMin(K key, long timeout, TimeUnit unit); + + /** + * Remove and return the value with its score having the lowest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout must not be {@literal null}. + * @return can be {@literal null}. + * @throws IllegalArgumentException if the timeout is {@literal null} or negative. + * @see Redis Documentation: BZPOPMIN + * @since 2.6 + */ + @Nullable + default TypedTuple popMin(K key, Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null"); + Assert.isTrue(!timeout.isNegative(), "Timeout must not be negative"); + + return popMin(key, TimeoutUtils.toSeconds(timeout), TimeUnit.SECONDS); + } + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + @Nullable + TypedTuple popMax(K key); + + /** + * Remove and return {@code count} values with their score having the highest score from sorted set at {@code key}. + * + * @param key must not be {@literal null}. + * @param count number of elements to pop. + * @return {@literal null} when the sorted set is empty or used in pipeline / transaction. + * @see Redis Documentation: ZPOPMAX + * @since 2.6 + */ + @Nullable + Set> popMax(K key, long count); + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout + * @param unit must not be {@literal null}. + * @return can be {@literal null}. + * @see Redis Documentation: BZPOPMAX + * @since 2.6 + */ + @Nullable + TypedTuple popMax(K key, long timeout, TimeUnit unit); + + /** + * Remove and return the value with its score having the highest score from sorted set at {@code key}. Blocks + * connection until element available or {@code timeout} reached. + * + * @param key must not be {@literal null}. + * @param timeout must not be {@literal null}. + * @return can be {@literal null}. + * @throws IllegalArgumentException if the timeout is {@literal null} or negative. + * @see Redis Documentation: BZPOPMAX + * @since 2.6 + */ + @Nullable + default TypedTuple popMax(K key, Duration timeout) { + + Assert.notNull(timeout, "Timeout must not be null"); + Assert.isTrue(!timeout.isNegative(), "Timeout must not be negative"); + + return popMin(key, TimeoutUtils.toSeconds(timeout), TimeUnit.SECONDS); + } + /** * Returns the number of elements of the sorted set stored with given {@code key}. * @@ -370,6 +489,18 @@ static TypedTuple of(V value, @Nullable Double score) { @Nullable Double score(K key, Object o); + /** + * Get the scores of elements with {@code values} from sorted set with key {@code key}. + * + * @param key must not be {@literal null}. + * @param o the values. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZMSCORE + * @since 2.6 + */ + @Nullable + List score(K key, Object... o); + /** * Remove elements in range between {@code start} and {@code end} from sorted set with {@code key}. * @@ -407,59 +538,150 @@ static TypedTuple of(V value, @Nullable Double score) { 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); + 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}. - * @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, Collection otherKeys, K destKey); + Set difference(K key, Collection otherKeys); /** - * 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}. + * @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}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.6 + * @see Redis Documentation: ZDIFFSTORE + */ + @Nullable + 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.1 - * @see Redis Documentation: ZUNIONSTORE + * @since 2.6 + * @see Redis Documentation: ZINTER */ @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> intersectWithScores(K key, Collection otherKeys, Aggregate aggregate) { + return intersectWithScores(key, otherKeys, aggregate, Weights.fromSetCount(1 + otherKeys.size())); } /** - * Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}. + * Intersect sorted {@code sets}. * * @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 + * @return + * @since 2.6 + * @see Redis Documentation: ZINTER */ @Nullable - Long unionAndStore(K key, Collection otherKeys, K destKey, Aggregate aggregate, Weights weights); + 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}. @@ -516,6 +738,142 @@ default Long intersectAndStore(K key, Collection otherKeys, K destKey, Aggreg @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. @@ -558,38 +916,38 @@ default Set rangeByLex(K key, Range range) { @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); + /** + * 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(); -} + /** + * @return never {@literal null}. + */ + RedisOperations getOperations(); + } diff --git a/src/main/java/org/springframework/data/redis/support/collections/AbstractRedisCollection.java b/src/main/java/org/springframework/data/redis/support/collections/AbstractRedisCollection.java index 4cc2e7397f..1361da4893 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/AbstractRedisCollection.java +++ b/src/main/java/org/springframework/data/redis/support/collections/AbstractRedisCollection.java @@ -22,6 +22,7 @@ import org.springframework.data.redis.core.RedisOperations; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Base implementation for {@link RedisCollection}. Provides a skeletal implementation. Note that the collection support @@ -47,6 +48,9 @@ public abstract class AbstractRedisCollection extends AbstractCollection i */ public AbstractRedisCollection(String key, RedisOperations operations) { + Assert.hasText(key, "Key must not be empty!"); + Assert.notNull(operations, "RedisOperations must not be null!"); + this.key = key; this.operations = operations; } @@ -217,7 +221,7 @@ public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("RedisStore for key:"); + sb.append(String.format("%s for key:", getClass().getSimpleName())); sb.append(getKey()); return sb.toString(); diff --git a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java index d9b9add8ad..d84a3fe489 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java @@ -19,6 +19,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.connection.RedisZSetCommands.Limit; @@ -42,7 +43,7 @@ public class DefaultRedisZSet extends AbstractRedisCollection implements RedisZSet { private final BoundZSetOperations boundZSetOps; - private double defaultScore = 1; + private final double defaultScore; private class DefaultRedisSortedSetIterator extends RedisIterator { @@ -104,6 +105,100 @@ public DefaultRedisZSet(BoundZSetOperations boundOps, double defaultS this.defaultScore = defaultScore; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#difference(org.springframework.data.redis.support.collections.RedisZSet) + */ + @Override + public Set difference(RedisZSet set) { + return boundZSetOps.difference(set.getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#difference(java.util.Collection) + */ + @Override + public Set difference(Collection> sets) { + return boundZSetOps.difference(CollectionUtils.extractKeys(sets)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#differenceWithScores(org.springframework.data.redis.support.collections.RedisZSet) + */ + @Override + public Set> differenceWithScores(RedisZSet set) { + return boundZSetOps.differenceWithScores(set.getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#differenceWithScores(java.util.Collection) + */ + @Override + public Set> differenceWithScores(Collection> sets) { + return boundZSetOps.differenceWithScores(CollectionUtils.extractKeys(sets)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#differenceAndStore(org.springframework.data.redis.support.collections.RedisZSet, java.lang.String) + */ + @Override + public RedisZSet differenceAndStore(RedisZSet set, String destKey) { + + boundZSetOps.differenceAndStore(set.getKey(), destKey); + return new DefaultRedisZSet<>(boundZSetOps.getOperations().boundZSetOps(destKey), getDefaultScore()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#differenceAndStore(java.util.Collection, java.lang.String) + */ + @Override + public RedisZSet differenceAndStore(Collection> sets, String destKey) { + + boundZSetOps.differenceAndStore(CollectionUtils.extractKeys(sets), destKey); + return new DefaultRedisZSet<>(boundZSetOps.getOperations().boundZSetOps(destKey), getDefaultScore()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#intersect(org.springframework.data.redis.support.collections.RedisZSet) + */ + @Override + public Set intersect(RedisZSet set) { + return boundZSetOps.intersect(set.getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#intersect(java.util.Collection) + */ + @Override + public Set intersect(Collection> sets) { + return boundZSetOps.intersect(CollectionUtils.extractKeys(sets)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#intersectWithScores(org.springframework.data.redis.support.collections.RedisZSet) + */ + @Override + public Set> intersectWithScores(RedisZSet set) { + return boundZSetOps.intersectWithScores(set.getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#intersectWithScores(java.util.Collection) + */ + @Override + public Set> intersectWithScores(Collection> sets) { + return boundZSetOps.intersectWithScores(CollectionUtils.extractKeys(sets)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.support.collections.RedisZSet#intersectAndStore(org.springframework.data.redis.support.collections.RedisZSet, java.lang.String) @@ -126,6 +221,62 @@ public RedisZSet intersectAndStore(Collection> sets, S return new DefaultRedisZSet<>(boundZSetOps.getOperations().boundZSetOps(destKey), getDefaultScore()); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#union(org.springframework.data.redis.support.collections.RedisZSet) + */ + @Override + public Set union(RedisZSet set) { + return boundZSetOps.union(set.getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#union(java.util.Collection) + */ + @Override + public Set union(Collection> sets) { + return boundZSetOps.union(CollectionUtils.extractKeys(sets)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#unionWithScores(org.springframework.data.redis.support.collections.RedisZSet) + */ + @Override + public Set> unionWithScores(RedisZSet set) { + return boundZSetOps.unionWithScores(set.getKey()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#unionWithScores(java.util.Collection) + */ + @Override + public Set> unionWithScores(Collection> sets) { + return boundZSetOps.unionWithScores(CollectionUtils.extractKeys(sets)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#unionAndStore(org.springframework.data.redis.support.collections.RedisZSet, java.lang.String) + */ + @Override + public RedisZSet unionAndStore(RedisZSet set, String destKey) { + boundZSetOps.unionAndStore(set.getKey(), destKey); + return new DefaultRedisZSet<>(boundZSetOps.getOperations().boundZSetOps(destKey), getDefaultScore()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#unionAndStore(java.util.Collection, java.lang.String) + */ + @Override + public RedisZSet unionAndStore(Collection> sets, String destKey) { + boundZSetOps.unionAndStore(CollectionUtils.extractKeys(sets), destKey); + return new DefaultRedisZSet<>(boundZSetOps.getOperations().boundZSetOps(destKey), getDefaultScore()); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.support.collections.RedisZSet#range(long, long) @@ -246,26 +397,6 @@ public RedisZSet removeByScore(double min, double max) { return this; } - /* - * (non-Javadoc) - * @see org.springframework.data.redis.support.collections.RedisZSet#unionAndStore(org.springframework.data.redis.support.collections.RedisZSet, java.lang.String) - */ - @Override - public RedisZSet unionAndStore(RedisZSet set, String destKey) { - boundZSetOps.unionAndStore(set.getKey(), destKey); - return new DefaultRedisZSet<>(boundZSetOps.getOperations().boundZSetOps(destKey), getDefaultScore()); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.support.collections.RedisZSet#unionAndStore(java.util.Collection, java.lang.String) - */ - @Override - public RedisZSet unionAndStore(Collection> sets, String destKey) { - boundZSetOps.unionAndStore(CollectionUtils.extractKeys(sets), destKey); - return new DefaultRedisZSet<>(boundZSetOps.getOperations().boundZSetOps(destKey), getDefaultScore()); - } - /* * (non-Javadoc) * @see java.util.AbstractCollection#add(java.lang.Object) @@ -380,6 +511,38 @@ public E first() { throw new NoSuchElementException(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#popFirst() + */ + @Override + public E popFirst() { + + TypedTuple tuple = boundZSetOps.popMin(); + + if (tuple != null) { + return tuple.getValue(); + } + + throw new NoSuchElementException(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#popFirst(long, java.util.concurrent.TimeUnit) + */ + @Override + public E popFirst(long timeout, TimeUnit unit) { + + TypedTuple tuple = boundZSetOps.popMin(timeout, unit); + + if (tuple != null) { + return tuple.getValue(); + } + + throw new NoSuchElementException(); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.support.collections.RedisZSet#last() @@ -397,6 +560,38 @@ public E last() { throw new NoSuchElementException(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#popLast() + */ + @Override + public E popLast() { + + TypedTuple tuple = boundZSetOps.popMax(); + + if (tuple != null) { + return tuple.getValue(); + } + + throw new NoSuchElementException(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#popLast(long, java.util.concurrent.TimeUnit) + */ + @Override + public E popLast(long timeout, TimeUnit unit) { + + TypedTuple tuple = boundZSetOps.popMax(timeout, unit); + + if (tuple != null) { + return tuple.getValue(); + } + + throw new NoSuchElementException(); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.support.collections.RedisZSet#rank(java.lang.Object) diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisCollectionFactoryBean.java b/src/main/java/org/springframework/data/redis/support/collections/RedisCollectionFactoryBean.java index bb352dc852..bf3df48096 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisCollectionFactoryBean.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisCollectionFactoryBean.java @@ -112,7 +112,7 @@ private RedisStore createStore(DataType dt) { return new DefaultRedisSet(key, template); case ZSET: - return new DefaultRedisZSet(key, template); + return RedisZSet.create(key, template); case HASH: if (CollectionType.PROPERTIES.equals(type)) { diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java b/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java index 86fc8646ef..89002ebfb1 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java @@ -21,10 +21,13 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; +import java.util.concurrent.TimeUnit; +import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.connection.RedisZSetCommands.Limit; import org.springframework.data.redis.connection.RedisZSetCommands.Range; import org.springframework.data.redis.core.BoundZSetOperations; +import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; /** @@ -40,16 +43,215 @@ */ public interface RedisZSet extends RedisCollection, Set { + /** + * Constructs a new {@link RedisZSet} instance with a default score of {@literal 1}. + * + * @param key Redis key of this set. + * @param operations {@link RedisOperations} for the value type of this set. + * @since 2.6 + */ + static RedisZSet create(String key, RedisOperations operations) { + return new DefaultRedisZSet<>(key, operations, 1); + } + + /** + * Constructs a new {@link RedisZSet} instance. + * + * @param key Redis key of this set. + * @param operations {@link RedisOperations} for the value type of this set. + * @param defaultScore + * @since 2.6 + */ + static RedisZSet create(String key, RedisOperations operations, double defaultScore) { + return new DefaultRedisZSet<>(key, operations, defaultScore); + } + + /** + * Diff this set and another {@link RedisZSet}. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the values that differ. + * @since 2.6 + */ + Set difference(RedisZSet set); + + /** + * Diff this set and other {@link RedisZSet}s. + * + * @param sets must not be {@literal null}. + * @return a {@link Set} containing the values that differ. + * @since 2.6 + */ + Set difference(Collection> sets); + + /** + * Diff this set and another {@link RedisZSet}. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the values that differ with their scores. + * @since 2.6 + */ + Set> differenceWithScores(RedisZSet set); + + /** + * Diff this set and other {@link RedisZSet}s. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the values that differ with their scores. + * @since 2.6 + */ + Set> differenceWithScores(Collection> sets); + + /** + * Create a new {@link RedisZSet} by diffing this sorted set and {@link RedisZSet} and store result in destination + * {@code destKey}. + * + * @param set must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisZSet} pointing at {@code destKey}. + * @since 2.6 + */ + RedisZSet differenceAndStore(RedisZSet set, String destKey); + + /** + * Create a new {@link RedisZSet} by diffing this sorted set and the collection {@link RedisZSet} and store result in + * destination {@code destKey}. + * + * @param sets must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisZSet} pointing at {@code destKey}. + * @since 2.6 + */ + RedisZSet differenceAndStore(Collection> sets, String destKey); + + /** + * Intersect this set and another {@link RedisZSet}. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the intersecting values. + * @since 2.6 + */ + Set intersect(RedisZSet set); + + /** + * Intersect this set and other {@link RedisZSet}s. + * + * @param sets must not be {@literal null}. + * @return a {@link Set} containing the intersecting values. + * @since 2.6 + */ + Set intersect(Collection> sets); + + /** + * Intersect this set and another {@link RedisZSet}. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the intersecting values with their scores. + * @since 2.6 + */ + Set> intersectWithScores(RedisZSet set); + + /** + * Intersect this set and other {@link RedisZSet}s. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the intersecting values with their scores. + * @since 2.6 + */ + Set> intersectWithScores(Collection> sets); + + /** + * Create a new {@link RedisZSet} by intersecting this sorted set and {@link RedisZSet} and store result in + * destination {@code destKey}. + * + * @param set must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisZSet} pointing at {@code destKey} + */ RedisZSet intersectAndStore(RedisZSet set, String destKey); + /** + * Create a new {@link RedisZSet} by intersecting this sorted set and the collection {@link RedisZSet} and store + * result in destination {@code destKey}. + * + * @param sets must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisZSet} pointing at {@code destKey} + */ RedisZSet intersectAndStore(Collection> sets, String destKey); + /** + * Union this set and another {@link RedisZSet}. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the combined values. + * @since 2.6 + */ + Set union(RedisZSet set); + + /** + * Union this set and other {@link RedisZSet}s. + * + * @param sets must not be {@literal null}. + * @return a {@link Set} containing the combined values. + * @since 2.6 + */ + Set union(Collection> sets); + + /** + * Union this set and another {@link RedisZSet}. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the combined values with their scores. + * @since 2.6 + */ + Set> unionWithScores(RedisZSet set); + + /** + * Union this set and other {@link RedisZSet}s. + * + * @param set must not be {@literal null}. + * @return a {@link Set} containing the combined values with their scores. + * @since 2.6 + */ + Set> unionWithScores(Collection> sets); + + /** + * Create a new {@link RedisZSet} by union this sorted set and {@link RedisZSet} and store result in destination + * {@code destKey}. + * + * @param set must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisZSet} pointing at {@code destKey} + */ RedisZSet unionAndStore(RedisZSet set, String destKey); + /** + * Create a new {@link RedisZSet} by union this sorted set and the collection {@link RedisZSet} and store result in + * destination {@code destKey}. + * + * @param sets must not be {@literal null}. + * @param destKey must not be {@literal null}. + * @return a new {@link RedisZSet} pointing at {@code destKey} + */ RedisZSet unionAndStore(Collection> sets, String destKey); + /** + * Get elements between {@code start} and {@code end} from sorted set. + * + * @param start + * @param end + * @return + */ Set range(long start, long end); + /** + * Get elements in range from {@code start} to {@code end} from sorted set ordered from high to low. + * + * @param start + * @param end + * @return + */ Set reverseRange(long start, long end); /** @@ -104,29 +306,88 @@ default Set reverseRangeByLex(Range range) { */ Set reverseRangeByLex(Range range, Limit limit); + /** + * Get elements where score is between {@code min} and {@code max} from sorted set. + * + * @param min + * @param max + * @return + */ Set rangeByScore(double min, double max); + /** + * Get elements where score is between {@code min} and {@code max} from sorted set ordered from high to low. + * + * @param min + * @param max + * @return + */ Set reverseRangeByScore(double min, double max); + /** + * Get set of {@link RedisZSetCommands.Tuple}s between {@code start} and {@code end} from sorted set. + * + * @param start + * @param end + * @return + */ Set> rangeWithScores(long start, long end); + /** + * Get set of {@link RedisZSetCommands.Tuple}s in range from {@code start} to {@code end} from sorted set ordered from + * high to low. + * + * @param start + * @param end + * @return + */ Set> reverseRangeWithScores(long start, long end); + /** + * Get set of {@link RedisZSetCommands.Tuple}s where score is between {@code min} and {@code max} from sorted set. + * + * @param min + * @param max + * @return + */ Set> rangeByScoreWithScores(double min, double max); + /** + * Get set of {@link RedisZSetCommands.Tuple}s where score is between {@code min} and {@code max} from sorted set + * ordered from high to low. + * + * @param min + * @param max + * @return + */ Set> reverseRangeByScoreWithScores(double min, double max); + /** + * Remove elements in range between {@code start} and {@code end} from sorted set. + * + * @param start + * @param end + * @return {@code this} set. + */ RedisZSet remove(long start, long end); /** * Remove all elements in range. * * @param range must not be {@literal null}. - * @return never {@literal null}. + * @return {@code this} set. * @since 2.5 */ + // TODO: Switch to RedisZSet Set removeByLex(Range range); + /** + * Remove elements with scores between {@code min} and {@code max} from sorted set with the bound key. + * + * @param min + * @param max + * @return {@code this} set. + */ RedisZSet removeByScore(double min, double max); /** @@ -217,6 +478,28 @@ default boolean addIfAbsent(E e) { */ E first(); + /** + * Removes the first (lowest) object at the top of this sorted set and returns that object as the value of this + * function. + * + * @return the first (lowest) element currently in this sorted set. + * @throws NoSuchElementException sorted set is empty. + * @since 2.6 + */ + E popFirst(); + + /** + * Removes the first (lowest) object at the top of this sorted set and returns that object as the value of this + * function. Blocks connection until element available or {@code timeout} reached. + * + * @param timeout + * @param unit must not be {@literal null}. + * @return the first (lowest) element currently in this sorted set. + * @throws NoSuchElementException sorted set is empty. + * @since 2.6 + */ + E popFirst(long timeout, TimeUnit unit); + /** * Returns the last (highest) element currently in this sorted set. * @@ -225,6 +508,28 @@ default boolean addIfAbsent(E e) { */ E last(); + /** + * Removes the last (highest) object at the top of this sorted set and returns that object as the value of this + * function. + * + * @return the last (highest) element currently in this sorted set. + * @throws NoSuchElementException sorted set is empty. + * @since 2.6 + */ + E popLast(); + + /** + * Removes the last (highest) object at the top of this sorted set and returns that object as the value of this + * function. Blocks connection until element available or {@code timeout} reached. + * + * @param timeout + * @param unit must not be {@literal null}. + * @return the last (highest) element currently in this sorted set. + * @throws NoSuchElementException sorted set is empty. + * @since 2.6 + */ + E popLast(long timeout, TimeUnit unit); + /** * @since 1.4 * @return 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 23f741034a..29b1911025 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -1801,6 +1801,52 @@ void zLexCountTest() { assertThat((Long) results.get(11)).isEqualTo(3); } + @Test // GH-2007 + @EnabledOnCommand("ZPOPMIN") + void zPopMin() { + + actual.add(connection.zAdd("myzset", 1, "a")); + actual.add(connection.zAdd("myzset", 2, "b")); + actual.add(connection.zAdd("myzset", 3, "c")); + actual.add(connection.zAdd("myzset", 4, "d")); + + actual.add(connection.zPopMin("myzset")); + actual.add(connection.bZPopMin("myzset", 1, TimeUnit.SECONDS)); + actual.add(connection.zPopMin("myzset", 2)); + actual.add(connection.zPopMin("myzset")); + + List results = getResults(); + + assertThat(results.get(4)).isEqualTo(new DefaultStringTuple("a".getBytes(), "a", 1D)); + assertThat(results.get(5)).isEqualTo(new DefaultStringTuple("b".getBytes(), "b", 2D)); + assertThat((Collection) results.get(6)).containsExactly(new DefaultStringTuple("c".getBytes(), "c", 3D), + new DefaultStringTuple("d".getBytes(), "d", 4D)); + assertThat(results.get(7)).isNull(); + } + + @Test // GH-2007 + @EnabledOnCommand("ZPOPMAX") + void zPopMax() { + + actual.add(connection.zAdd("myzset", 1, "a")); + actual.add(connection.zAdd("myzset", 2, "b")); + actual.add(connection.zAdd("myzset", 3, "c")); + actual.add(connection.zAdd("myzset", 4, "d")); + + actual.add(connection.zPopMax("myzset")); + actual.add(connection.bZPopMax("myzset", 1, TimeUnit.SECONDS)); + actual.add(connection.zPopMax("myzset", 2)); + actual.add(connection.zPopMax("myzset")); + + List results = getResults(); + + assertThat(results.get(4)).isEqualTo(new DefaultStringTuple("d".getBytes(), "d", 4D)); + assertThat(results.get(5)).isEqualTo(new DefaultStringTuple("c".getBytes(), "c", 3D)); + assertThat((Collection) results.get(6)).containsExactly(new DefaultStringTuple("b".getBytes(), "b", 2D), + new DefaultStringTuple("a".getBytes(), "a", 1D)); + assertThat(results.get(7)).isNull(); + } + @Test void testZIncrBy() { actual.add(connection.zAdd("myset", 2, "Bob")); @@ -1812,6 +1858,64 @@ void testZIncrBy() { 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")); @@ -2025,6 +2129,49 @@ void testZScore() { verifyResults(Arrays.asList(new Object[] { true, true, true, 3d })); } + @Test + @EnabledOnCommand("ZMSCORE") + void testZMScore() { + actual.add(connection.zAdd("myset", 2, "Bob")); + actual.add(connection.zAdd("myset", 1, "James")); + actual.add(connection.zAdd("myset", 3, "Joe")); + actual.add(connection.zMScore("myset", "James", "Joe")); + verifyResults(Arrays.asList(new Object[] { true, true, true, Arrays.asList(1d, 3d) })); + } + + @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() { actual.add(connection.zAdd("myset", 2, "Bob")); @@ -2048,9 +2195,8 @@ void testZUnionStoreAggWeights() { 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 f6ab929d2e..a253d75467 100644 --- a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java @@ -598,12 +598,39 @@ 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(); // DATAREDIS-315 void zInterStoreShouldWorkForSameSlotKeys(); + // GH-2007 + void zPopMinShouldWorkCorrectly(); + + // GH-2007 + void bzPopMinShouldWorkCorrectly(); + + // GH-2007 + void zPopMaxShouldWorkCorrectly(); + + // GH-2007 + void bzPopMaxShouldWorkCorrectly(); + // DATAREDIS-315 void zRangeByLexShouldReturnResultCorrectly(); @@ -664,6 +691,15 @@ public interface ClusterConnectionTests { // DATAREDIS-315 void zScoreShouldRetrieveScoreForValue(); + // 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/DefaultStringRedisConnectionPipelineTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java index e7f2707653..e8ca76ce97 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java @@ -1330,6 +1330,12 @@ public void testZScore() { super.testZScore(); } + @Test + public void testZMScore() { + doReturn(Collections.singletonList(Arrays.asList(1d, 3d))).when(nativeConnection).closePipeline(); + super.testZMScore(); + } + @Test public void testZUnionStoreAggWeightsBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java index 2691e66c46..0b5a5d6514 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java @@ -1433,6 +1433,13 @@ public void testZScore() { super.testZScore(); } + @Test + public void testZMScore() { + doReturn(Collections.singletonList(Collections.singletonList(Arrays.asList(1d, 3d)))).when(nativeConnection) + .closePipeline(); + super.testZMScore(); + } + @Test public void testZUnionStoreAggWeightsBytes() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java index fe09f3610c..b4ddc021ef 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java @@ -1624,6 +1624,13 @@ public void testZScore() { verifyResults(Collections.singletonList(3d)); } + @Test + public void testZMScore() { + doReturn(Arrays.asList(1d, 3d)).when(nativeConnection).zMScore(fooBytes, barBytes, bar2Bytes); + actual.add(connection.zMScore(foo, bar, bar2)); + verifyResults(Collections.singletonList(Arrays.asList(1d, 3d))); + } + @Test public void testZUnionStoreAggWeightsBytes() { doReturn(5L).when(nativeConnection).zUnionStore(eq(fooBytes), eq(Aggregate.MAX), any(Weights.class), eq(fooBytes)); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java index c846f0bb29..5c8e8519b1 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java @@ -1316,6 +1316,12 @@ public void testZScore() { super.testZScore(); } + @Test + public void testZMScore() { + doReturn(Collections.singletonList(Arrays.asList(1d, 3d))).when(nativeConnection).exec(); + super.testZMScore(); + } + @Test public void testZUnionStoreAggWeightsBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); diff --git a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java index 019f0f4916..7f787b794c 100644 --- a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java @@ -833,6 +833,10 @@ public Double zScore(byte[] key, byte[] value) { return delegate.zScore(key, value); } + public List zMScore(byte[] key, byte[][] values) { + return delegate.zMScore(key, values); + } + public Long zRemRange(byte[] key, long begin, long end) { return delegate.zRemRange(key, begin, end); } 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 e64c4754fd..2e91f8cb73 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 @@ -1986,7 +1986,67 @@ public void zIncrByShouldIncScoreForValueCorrectly() { 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)); @@ -2006,6 +2066,56 @@ public void zInterStoreShouldWorkForSameSlotKeys() { assertThat(nativeConnection.zrange(SAME_SLOT_KEY_3_BYTES, 0, -1)).contains(VALUE_2_BYTES); } + @Test // GH-2007 + @EnabledOnCommand("ZPOPMIN") + public void zPopMinShouldWorkCorrectly() { + + nativeConnection.zadd(KEY_1_BYTES, 10D, VALUE_1_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 20D, VALUE_2_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 30D, VALUE_3_BYTES); + + assertThat(clusterConnection.zPopMin(KEY_1_BYTES)).isEqualTo(new DefaultTuple(VALUE_1_BYTES, 10D)); + assertThat(clusterConnection.zPopMin(KEY_1_BYTES, 2)).containsExactly(new DefaultTuple(VALUE_2_BYTES, 20D), + new DefaultTuple(VALUE_3_BYTES, 30D)); + } + + @Test // GH-2007 + @EnabledOnCommand("BZPOPMIN") + public void bzPopMinShouldWorkCorrectly() { + + nativeConnection.zadd(KEY_1_BYTES, 10D, VALUE_1_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 20D, VALUE_2_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 30D, VALUE_3_BYTES); + + assertThat(clusterConnection.bZPopMin(KEY_1_BYTES, 1, TimeUnit.SECONDS)) + .isEqualTo(new DefaultTuple(VALUE_1_BYTES, 10D)); + } + + @Test // GH-2007 + @EnabledOnCommand("ZPOPMAX") + public void zPopMaxShouldWorkCorrectly() { + + nativeConnection.zadd(KEY_1_BYTES, 10D, VALUE_1_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 20D, VALUE_2_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 30D, VALUE_3_BYTES); + + assertThat(clusterConnection.zPopMax(KEY_1_BYTES)).isEqualTo(new DefaultTuple(VALUE_3_BYTES, 30D)); + assertThat(clusterConnection.zPopMax(KEY_1_BYTES, 2)).containsExactly(new DefaultTuple(VALUE_2_BYTES, 20D), + new DefaultTuple(VALUE_1_BYTES, 10D)); + } + + @Test // GH-2007 + @EnabledOnCommand("BZPOPMAX") + public void bzPopMaxShouldWorkCorrectly() { + + nativeConnection.zadd(KEY_1_BYTES, 10D, VALUE_1_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 20D, VALUE_2_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 30D, VALUE_3_BYTES); + + assertThat(clusterConnection.bZPopMax(KEY_1_BYTES, 1, TimeUnit.SECONDS)) + .isEqualTo(new DefaultTuple(VALUE_3_BYTES, 30D)); + } + @Test // DATAREDIS-315 public void zRangeByLexShouldReturnResultCorrectly() { @@ -2284,6 +2394,39 @@ public void zScoreShouldRetrieveScoreForValue() { assertThat(clusterConnection.zScore(KEY_1_BYTES, VALUE_2_BYTES)).isEqualTo(20D); } + @Test // GH-2038 + @EnabledOnCommand("ZMSCORE") + public void zMScoreShouldRetrieveScoreForValues() { + + nativeConnection.zadd(KEY_1_BYTES, 10D, VALUE_1_BYTES); + nativeConnection.zadd(KEY_1_BYTES, 20D, VALUE_2_BYTES); + + 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 4b4de458ad..38ec5c0afa 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 @@ -58,6 +58,7 @@ import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.types.Expiration; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.condition.EnabledOnRedisClusterAvailable; import org.springframework.data.redis.test.extension.LettuceExtension; import org.springframework.data.redis.test.extension.LettuceTestClientResources; @@ -2026,6 +2027,66 @@ public void zIncrByShouldIncScoreForValueCorrectly() { 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) @@ -2046,6 +2107,56 @@ public void zInterStoreShouldWorkForSameSlotKeys() { assertThat(nativeConnection.zrange(SAME_SLOT_KEY_3, 0, -1)).contains(VALUE_2); } + @Test // GH-2007 + @EnabledOnCommand("ZPOPMIN") + public void zPopMinShouldWorkCorrectly() { + + nativeConnection.zadd(KEY_1, 10D, VALUE_1); + nativeConnection.zadd(KEY_1, 20D, VALUE_2); + nativeConnection.zadd(KEY_1, 30D, VALUE_3); + + assertThat(clusterConnection.zPopMin(KEY_1_BYTES)).isEqualTo(new DefaultTuple(VALUE_1_BYTES, 10D)); + assertThat(clusterConnection.zPopMin(KEY_1_BYTES, 2)).containsExactly(new DefaultTuple(VALUE_2_BYTES, 20D), + new DefaultTuple(VALUE_3_BYTES, 30D)); + } + + @Test // GH-2007 + @EnabledOnCommand("BZPOPMIN") + public void bzPopMinShouldWorkCorrectly() { + + nativeConnection.zadd(KEY_1, 10D, VALUE_1); + nativeConnection.zadd(KEY_1, 20D, VALUE_2); + nativeConnection.zadd(KEY_1, 30D, VALUE_3); + + assertThat(clusterConnection.bZPopMin(KEY_1_BYTES, 1, TimeUnit.SECONDS)) + .isEqualTo(new DefaultTuple(VALUE_1_BYTES, 10D)); + } + + @Test // GH-2007 + @EnabledOnCommand("ZPOPMAX") + public void zPopMaxShouldWorkCorrectly() { + + nativeConnection.zadd(KEY_1, 10D, VALUE_1); + nativeConnection.zadd(KEY_1, 20D, VALUE_2); + nativeConnection.zadd(KEY_1, 30D, VALUE_3); + + assertThat(clusterConnection.zPopMax(KEY_1_BYTES)).isEqualTo(new DefaultTuple(VALUE_3_BYTES, 30D)); + assertThat(clusterConnection.zPopMax(KEY_1_BYTES, 2)).containsExactly(new DefaultTuple(VALUE_2_BYTES, 20D), + new DefaultTuple(VALUE_1_BYTES, 10D)); + } + + @Test // GH-2007 + @EnabledOnCommand("BZPOPMAX") + public void bzPopMaxShouldWorkCorrectly() { + + nativeConnection.zadd(KEY_1, 10D, VALUE_1); + nativeConnection.zadd(KEY_1, 20D, VALUE_2); + nativeConnection.zadd(KEY_1, 30D, VALUE_3); + + assertThat(clusterConnection.bZPopMax(KEY_1_BYTES, 1, TimeUnit.SECONDS)) + .isEqualTo(new DefaultTuple(VALUE_3_BYTES, 30D)); + } + @Test // DATAREDIS-315 public void zRangeByLexShouldReturnResultCorrectly() { @@ -2324,6 +2435,39 @@ public void zScoreShouldRetrieveScoreForValue() { assertThat(clusterConnection.zScore(KEY_1_BYTES, VALUE_2_BYTES)).isEqualTo(20D); } + @Test // GH-2038 + @EnabledOnCommand("ZMSCORE") + public void zMScoreShouldRetrieveScoreForValues() { + + nativeConnection.zadd(KEY_1, 10D, VALUE_1); + nativeConnection.zadd(KEY_1, 20D, VALUE_2); + + 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/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java index 0e4cb2de42..acc003d28a 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java @@ -22,11 +22,13 @@ import reactor.test.StepVerifier; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Arrays; import org.springframework.data.domain.Range; import org.springframework.data.redis.connection.DefaultTuple; import org.springframework.data.redis.core.ScanOptions; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; /** @@ -384,6 +386,62 @@ void zCountShouldCountValuesInRangeWithPositiveInfinity() { .isEqualTo(2L); } + @ParameterizedRedisTest // GH-2007 + @EnabledOnCommand("ZPOPMIN") + void zPopMinShouldReturnCorrectly() { + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + nativeCommands.zadd(KEY_1, 3D, VALUE_3); + + connection.zSetCommands().zPopMin(KEY_1_BBUFFER).as(StepVerifier::create) + .expectNext(new DefaultTuple(VALUE_1_BYTES, 1D)).verifyComplete(); + + connection.zSetCommands().zPopMin(KEY_1_BBUFFER, 2).as(StepVerifier::create) + .expectNext(new DefaultTuple(VALUE_2_BYTES, 2D)).expectNext(new DefaultTuple(VALUE_3_BYTES, 3D)) + .verifyComplete(); + } + + @ParameterizedRedisTest // GH-2007 + @EnabledOnCommand("BZPOPMIN") + void bzPopMinShouldReturnCorrectly() { + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + nativeCommands.zadd(KEY_1, 3D, VALUE_3); + + connection.zSetCommands().bZPopMin(KEY_1_BBUFFER, Duration.ofSeconds(1)).as(StepVerifier::create) + .expectNext(new DefaultTuple(VALUE_1_BYTES, 1D)).verifyComplete(); + } + + @ParameterizedRedisTest // GH-2007 + @EnabledOnCommand("ZPOPMAX") + void zPopMaxShouldReturnCorrectly() { + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + nativeCommands.zadd(KEY_1, 3D, VALUE_3); + + connection.zSetCommands().zPopMax(KEY_1_BBUFFER).as(StepVerifier::create) + .expectNext(new DefaultTuple(VALUE_3_BYTES, 3D)).verifyComplete(); + + connection.zSetCommands().zPopMax(KEY_1_BBUFFER, 2).as(StepVerifier::create) + .expectNext(new DefaultTuple(VALUE_2_BYTES, 2D)).expectNext(new DefaultTuple(VALUE_1_BYTES, 1D)) + .verifyComplete(); + } + + @ParameterizedRedisTest // GH-2007 + @EnabledOnCommand("BZPOPMAX") + void bzPopMaxShouldReturnCorrectly() { + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + nativeCommands.zadd(KEY_1, 3D, VALUE_3); + + connection.zSetCommands().bZPopMax(KEY_1_BBUFFER, Duration.ofSeconds(1)).as(StepVerifier::create) + .expectNext(new DefaultTuple(VALUE_3_BYTES, 3D)).verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-525 void zCardShouldReturnSizeCorrectly() { @@ -402,6 +460,17 @@ void zScoreShouldReturnScoreCorrectly() { assertThat(connection.zSetCommands().zScore(KEY_1_BBUFFER, VALUE_2_BBUFFER).block()).isEqualTo(2D); } + @ParameterizedRedisTest // GH-2038 + @EnabledOnCommand("ZMSCORE") + void zMScoreShouldReturnScoreCorrectly() { + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + + connection.zSetCommands().zMScore(KEY_1_BBUFFER, Arrays.asList(VALUE_1_BBUFFER, VALUE_2_BBUFFER)) + .as(StepVerifier::create).expectNext(Arrays.asList(1D, 2D)).verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-525 void zRemRangeByRankShouldRemoveValuesCorrectly() { @@ -486,8 +555,50 @@ void zRemRangeByScoreShouldRemoveValuesCorrectlyWithExcludingMaxRange() { .isEqualTo(1L); } - @ParameterizedRedisTest // DATAREDIS-525 - void zUnionStoreShouldWorkCorrectly() { + @ParameterizedRedisTest // GH-2041 + void zDiffShouldWorkCorrectly() { + + assumeThat(connectionProvider).isInstanceOf(StandaloneConnectionProvider.class); + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + nativeCommands.zadd(KEY_1, 3D, VALUE_3); + nativeCommands.zadd(KEY_2, 1D, VALUE_1); + nativeCommands.zadd(KEY_2, 2D, VALUE_2); + + connection.zSetCommands().zDiff(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER)) // + .collectList() // + .as(StepVerifier::create) // + .assertNext(actual -> { + assertThat(actual).containsOnly(VALUE_3_BBUFFER); + }).verifyComplete(); + + connection.zSetCommands().zDiffWithScores(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER)) // + .collectList() // + .as(StepVerifier::create) // + .assertNext(actual -> { + assertThat(actual).containsOnly(new DefaultTuple(VALUE_3_BYTES, 3D)); + }).verifyComplete(); + } + + @ParameterizedRedisTest // GH-2041 + void zDiffStoreShouldWorkCorrectly() { + + assumeThat(connectionProvider).isInstanceOf(StandaloneConnectionProvider.class); + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + nativeCommands.zadd(KEY_1, 3D, VALUE_3); + nativeCommands.zadd(KEY_2, 1D, VALUE_1); + nativeCommands.zadd(KEY_2, 2D, VALUE_2); + + connection.zSetCommands().zDiffStore(KEY_3_BBUFFER, Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER)) // + .as(StepVerifier::create) // + .expectNext(1L).verifyComplete(); + } + + @ParameterizedRedisTest // GH-2042 + void zInterShouldWorkCorrectly() { assumeThat(connectionProvider).isInstanceOf(StandaloneConnectionProvider.class); @@ -497,9 +608,19 @@ void zUnionStoreShouldWorkCorrectly() { nativeCommands.zadd(KEY_2, 2D, VALUE_2); nativeCommands.zadd(KEY_2, 3D, VALUE_3); - assertThat(connection.zSetCommands() - .zUnionStore(KEY_3_BBUFFER, Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), Arrays.asList(2D, 3D)).block()) - .isEqualTo(3L); + connection.zSetCommands().zInter(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER)) // + .collectList() // + .as(StepVerifier::create) // + .assertNext(actual -> { + assertThat(actual).contains(VALUE_1_BBUFFER, VALUE_2_BBUFFER); + }).verifyComplete(); + + connection.zSetCommands().zInterWithScores(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), Arrays.asList(2D, 3D)) // + .collectList() // + .as(StepVerifier::create) // + .assertNext(actual -> { + assertThat(actual).contains(new DefaultTuple(VALUE_1_BYTES, 5D), new DefaultTuple(VALUE_2_BYTES, 10D)); + }).verifyComplete(); } @ParameterizedRedisTest // DATAREDIS-525 @@ -518,6 +639,49 @@ void zInterStoreShouldWorkCorrectly() { .isEqualTo(2L); } + @ParameterizedRedisTest // GH-2042 + void zUnionShouldWorkCorrectly() { + + assumeThat(connectionProvider).isInstanceOf(StandaloneConnectionProvider.class); + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + nativeCommands.zadd(KEY_2, 1D, VALUE_1); + nativeCommands.zadd(KEY_2, 2D, VALUE_2); + nativeCommands.zadd(KEY_2, 3D, VALUE_3); + + connection.zSetCommands().zUnion(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER)) // + .collectList() // + .as(StepVerifier::create) // + .assertNext(actual -> { + assertThat(actual).contains(VALUE_1_BBUFFER, VALUE_2_BBUFFER, VALUE_3_BBUFFER); + }).verifyComplete(); + + connection.zSetCommands().zUnionWithScores(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), Arrays.asList(2D, 3D)) // + .collectList() // + .as(StepVerifier::create) // + .assertNext(actual -> { + assertThat(actual).contains(new DefaultTuple(VALUE_1_BYTES, 5D), new DefaultTuple(VALUE_2_BYTES, 10D), + new DefaultTuple(VALUE_3_BYTES, 9D)); + }).verifyComplete(); + } + + @ParameterizedRedisTest // DATAREDIS-525 + void zUnionStoreShouldWorkCorrectly() { + + assumeThat(connectionProvider).isInstanceOf(StandaloneConnectionProvider.class); + + nativeCommands.zadd(KEY_1, 1D, VALUE_1); + nativeCommands.zadd(KEY_1, 2D, VALUE_2); + nativeCommands.zadd(KEY_2, 1D, VALUE_1); + nativeCommands.zadd(KEY_2, 2D, VALUE_2); + nativeCommands.zadd(KEY_2, 3D, VALUE_3); + + assertThat(connection.zSetCommands() + .zUnionStore(KEY_3_BBUFFER, Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), Arrays.asList(2D, 3D)).block()) + .isEqualTo(3L); + } + @ParameterizedRedisTest // DATAREDIS-525 void zRangeByLex() { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java index 2950a54d0a..2667d81030 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultReactiveZSetOperationsIntegrationTests.java @@ -20,6 +20,7 @@ import reactor.test.StepVerifier; +import java.time.Duration; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -38,6 +39,7 @@ import org.springframework.data.redis.core.ReactiveOperationsTestParams.Fixture; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.parametrized.MethodSource; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; @@ -433,6 +435,52 @@ void lexCount() { zSetOperations.lexCount(key, Range.rightOpen("b", "f")).as(StepVerifier::create).expectNext(4L).verifyComplete(); } + @ParameterizedRedisTest // GH-2007 + @EnabledOnCommand("ZPOPMIN") + void popMin() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + V value4 = valueFactory.instance(); + + zSetOperations.add(key, value1, 1).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, value2, 2).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, value3, 3).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, value4, 4).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.popMin(key).as(StepVerifier::create).expectNext(new DefaultTypedTuple<>(value1, 1D)) + .verifyComplete(); + zSetOperations.popMin(key, Duration.ofSeconds(1)).as(StepVerifier::create) + .expectNext(new DefaultTypedTuple<>(value2, 2D)).verifyComplete(); + zSetOperations.popMin(key, 2).as(StepVerifier::create).expectNext(new DefaultTypedTuple<>(value3, 3D)) + .expectNext(new DefaultTypedTuple<>(value4, 4D)).verifyComplete(); + } + + @ParameterizedRedisTest // GH-2007 + @EnabledOnCommand("ZPOPMAX") + void popMax() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + V value4 = valueFactory.instance(); + + zSetOperations.add(key, value1, 1).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, value2, 2).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, value3, 3).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, value4, 4).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.popMax(key).as(StepVerifier::create).expectNext(new DefaultTypedTuple<>(value4, 4D)) + .verifyComplete(); + zSetOperations.popMax(key, Duration.ofSeconds(1)).as(StepVerifier::create) + .expectNext(new DefaultTypedTuple<>(value3, 3D)).verifyComplete(); + zSetOperations.popMax(key, 2).as(StepVerifier::create).expectNext(new DefaultTypedTuple<>(value2, 2D)) + .expectNext(new DefaultTypedTuple<>(value1, 1D)).verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-602 void size() { @@ -460,6 +508,21 @@ void score() { zSetOperations.score(key, value2).as(StepVerifier::create).expectNext(10d).verifyComplete(); } + @ParameterizedRedisTest // GH-2038 + @EnabledOnCommand("ZMSCORE") + void scores() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + + zSetOperations.add(key, value1, 42.1).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, value2, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.score(key, value1, value2, valueFactory.instance()).as(StepVerifier::create) + .expectNext(Arrays.asList(42.1d, 10d, null)).verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-602 void removeRange() { @@ -496,12 +559,11 @@ void removeRangeByScore() { .verifyComplete(); } - @ParameterizedRedisTest // DATAREDIS-602 - void unionAndStore() { + @ParameterizedRedisTest // GH-2041 + void difference() { K key = keyFactory.instance(); K otherKey = keyFactory.instance(); - K destKey = keyFactory.instance(); V onlyInKey = valueFactory.instance(); V shared = valueFactory.instance(); @@ -513,12 +575,14 @@ void unionAndStore() { zSetOperations.add(otherKey, onlyInOtherKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); zSetOperations.add(otherKey, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); - zSetOperations.unionAndStore(key, otherKey, destKey).as(StepVerifier::create).expectNext(3L).verifyComplete(); - zSetOperations.range(destKey, Range.closed(0L, 100L)).as(StepVerifier::create).expectNextCount(3).verifyComplete(); + zSetOperations.difference(key, otherKey).as(StepVerifier::create).expectNext(onlyInKey).verifyComplete(); + + zSetOperations.differenceWithScores(key, otherKey).as(StepVerifier::create) + .expectNext(new DefaultTypedTuple<>(onlyInKey, 10D)).verifyComplete(); } - @ParameterizedRedisTest // DATAREDIS-746 - void unionAndStoreWithAggregation() { + @ParameterizedRedisTest // GH-2041 + void differenceAndStore() { K key = keyFactory.instance(); K otherKey = keyFactory.instance(); @@ -534,14 +598,37 @@ void unionAndStoreWithAggregation() { zSetOperations.add(otherKey, onlyInOtherKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); zSetOperations.add(otherKey, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); - zSetOperations.unionAndStore(key, Collections.singleton(otherKey), destKey, Aggregate.SUM).as(StepVerifier::create) - .expectNext(3L).verifyComplete(); - zSetOperations.score(destKey, shared).as(StepVerifier::create).expectNext(22d).verifyComplete(); + zSetOperations.differenceAndStore(key, otherKey, destKey).as(StepVerifier::create).expectNext(1L).verifyComplete(); - zSetOperations.unionAndStore(key, Collections.singleton(otherKey), destKey, Aggregate.SUM, Weights.of(2, 1)) - .as(StepVerifier::create) - .expectNext(3L).verifyComplete(); - zSetOperations.score(destKey, shared).as(StepVerifier::create).expectNext(33d).verifyComplete(); + zSetOperations.range(destKey, ZERO_TO_FIVE).as(StepVerifier::create) // + .expectNextCount(1) // + .verifyComplete(); + } + + @ParameterizedRedisTest // GH-2042 + @EnabledOnCommand("ZINTER") + void intersect() { + + K key = keyFactory.instance(); + K otherKey = keyFactory.instance(); + + V onlyInKey = valueFactory.instance(); + V shared = valueFactory.instance(); + V onlyInOtherKey = valueFactory.instance(); + + zSetOperations.add(key, onlyInKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.add(otherKey, onlyInOtherKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(otherKey, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.intersect(key, otherKey).as(StepVerifier::create).expectNext(shared).verifyComplete(); + + zSetOperations.intersectWithScores(key, otherKey).as(StepVerifier::create) + .expectNext(new DefaultTypedTuple<>(shared, 22D)).verifyComplete(); + + zSetOperations.intersectWithScores(key, Collections.singleton(otherKey), Aggregate.SUM, Weights.of(1, 2)) + .as(StepVerifier::create).expectNext(new DefaultTypedTuple<>(shared, 33D)).verifyComplete(); } @ParameterizedRedisTest // DATAREDIS-602 @@ -587,8 +674,7 @@ void intersectAndStoreWithAggregation() { zSetOperations.add(otherKey, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); zSetOperations.intersectAndStore(key, Collections.singletonList(otherKey), destKey, Aggregate.SUM) - .as(StepVerifier::create) - .expectNext(1L).expectComplete().verify(); + .as(StepVerifier::create).expectNext(1L).expectComplete().verify(); zSetOperations.score(destKey, shared).as(StepVerifier::create) // .expectNext(22d) // @@ -602,6 +688,87 @@ void intersectAndStoreWithAggregation() { .verifyComplete(); } + @ParameterizedRedisTest // GH-2042 + @EnabledOnCommand("ZUNION") + void union() { + + K key = keyFactory.instance(); + K otherKey = keyFactory.instance(); + + V onlyInKey = valueFactory.instance(); + V shared = valueFactory.instance(); + V onlyInOtherKey = valueFactory.instance(); + + zSetOperations.add(key, onlyInKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.add(otherKey, onlyInOtherKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(otherKey, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.union(key, otherKey).as(StepVerifier::create).expectNextCount(3).verifyComplete(); + + zSetOperations.unionWithScores(key, otherKey).collectList().as(StepVerifier::create).assertNext(actual -> { + assertThat(actual).containsOnly(new DefaultTypedTuple<>(onlyInKey, 10D), new DefaultTypedTuple<>(shared, 22D), + new DefaultTypedTuple<>(onlyInOtherKey, 10D)); + + }).verifyComplete(); + + zSetOperations.unionWithScores(key, Collections.singleton(otherKey), Aggregate.SUM, Weights.of(1, 2)).collectList() + .as(StepVerifier::create).assertNext(actual -> { + assertThat(actual).containsOnly(new DefaultTypedTuple<>(onlyInKey, 10D), new DefaultTypedTuple<>(shared, 33D), + new DefaultTypedTuple<>(onlyInOtherKey, 20D)); + + }).verifyComplete(); + } + + @ParameterizedRedisTest // DATAREDIS-602 + void unionAndStore() { + + K key = keyFactory.instance(); + K otherKey = keyFactory.instance(); + K destKey = keyFactory.instance(); + + V onlyInKey = valueFactory.instance(); + V shared = valueFactory.instance(); + V onlyInOtherKey = valueFactory.instance(); + + zSetOperations.add(key, onlyInKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.add(otherKey, onlyInOtherKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(otherKey, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.unionAndStore(key, otherKey, destKey).as(StepVerifier::create).expectNext(3L).verifyComplete(); + zSetOperations.range(destKey, Range.closed(0L, 100L)).as(StepVerifier::create).expectNextCount(3).verifyComplete(); + } + + @ParameterizedRedisTest // DATAREDIS-746 + void unionAndStoreWithAggregation() { + + K key = keyFactory.instance(); + K otherKey = keyFactory.instance(); + K destKey = keyFactory.instance(); + + V onlyInKey = valueFactory.instance(); + V shared = valueFactory.instance(); + V onlyInOtherKey = valueFactory.instance(); + + zSetOperations.add(key, onlyInKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(key, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.add(otherKey, onlyInOtherKey, 10).as(StepVerifier::create).expectNext(true).verifyComplete(); + zSetOperations.add(otherKey, shared, 11).as(StepVerifier::create).expectNext(true).verifyComplete(); + + zSetOperations.unionAndStore(key, Collections.singleton(otherKey), destKey, Aggregate.SUM).as(StepVerifier::create) + .expectNext(3L).verifyComplete(); + zSetOperations.score(destKey, shared).as(StepVerifier::create).expectNext(22d).verifyComplete(); + + zSetOperations.unionAndStore(key, Collections.singleton(otherKey), destKey, Aggregate.SUM, Weights.of(2, 1)) + .as(StepVerifier::create) + .expectNext(3L).verifyComplete(); + zSetOperations.score(destKey, shared).as(StepVerifier::create).expectNext(33d).verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-602 void rangeByLex() { 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 aa1860e006..600337cb46 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; @@ -35,6 +36,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.connection.RedisZSetCommands.Weights; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.parametrized.MethodSource; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; @@ -128,6 +130,48 @@ void testLexCountBounded() { assertThat(zSetOps.lexCount(key, RedisZSetCommands.Range.range().gt(value1))).isEqualTo(2); } + @ParameterizedRedisTest // GH-2007 + @EnabledOnCommand("ZPOPMIN") + void testPopMin() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + V value4 = valueFactory.instance(); + + zSetOps.add(key, value1, 1); + zSetOps.add(key, value2, 2); + zSetOps.add(key, value3, 3); + zSetOps.add(key, value4, 4); + + assertThat(zSetOps.popMin(key)).isEqualTo(new DefaultTypedTuple<>(value1, 1d)); + assertThat(zSetOps.popMin(key, 2)).containsExactly(new DefaultTypedTuple<>(value2, 2d), + new DefaultTypedTuple<>(value3, 3d)); + assertThat(zSetOps.popMin(key, 1, TimeUnit.SECONDS)).isEqualTo(new DefaultTypedTuple<>(value4, 4d)); + } + + @ParameterizedRedisTest // GH-2007 + @EnabledOnCommand("ZPOPMAX") + void testPopMax() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + V value4 = valueFactory.instance(); + + zSetOps.add(key, value1, 1); + zSetOps.add(key, value2, 2); + zSetOps.add(key, value3, 3); + zSetOps.add(key, value4, 4); + + assertThat(zSetOps.popMax(key)).isEqualTo(new DefaultTypedTuple<>(value4, 4d)); + assertThat(zSetOps.popMax(key, 2)).containsExactly(new DefaultTypedTuple<>(value3, 3d), + new DefaultTypedTuple<>(value2, 2d)); + assertThat(zSetOps.popMax(key, 1, TimeUnit.SECONDS)).isEqualTo(new DefaultTypedTuple<>(value1, 1d)); + } + @ParameterizedRedisTest void testIncrementScore() { @@ -339,6 +383,21 @@ void testRemove() { assertThat(zSetOps.range(key, 0, -1)).containsOnly(value2); } + @ParameterizedRedisTest + void testScore() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + + zSetOps.add(key, value1, 1.9); + zSetOps.add(key, value2, 3.7); + zSetOps.add(key, value3, 5.8); + + assertThat(zSetOps.score(key, value1, value2, valueFactory.instance())).containsExactly(1.9d, 3.7d, null); + } + @ParameterizedRedisTest void zCardRetrievesDataCorrectly() { @@ -403,8 +462,9 @@ void testZScanShouldReadEntireValueRange() throws IOException { assertThat(count).isEqualTo(3); } - @ParameterizedRedisTest // DATAREDIS-746 - void testZsetUnionWithAggregate() { + @ParameterizedRedisTest // GH-2042 + @EnabledOnCommand("ZDIFF") + void testZsetDiff() { K key1 = keyFactory.instance(); K key2 = keyFactory.instance(); @@ -416,13 +476,70 @@ void testZsetUnionWithAggregate() { zSetOps.add(key1, value2, 2.0); zSetOps.add(key2, value2, 3.0); - zSetOps.unionAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MIN); + 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 testZsetUnionWithAggregateWeights() { + void testZsetIntersectWithAggregateWeights() { K key1 = keyFactory.instance(); K key2 = keyFactory.instance(); @@ -431,14 +548,15 @@ void testZsetUnionWithAggregateWeights() { zSetOps.add(key1, value1, 4.0); zSetOps.add(key2, value1, 3.0); - zSetOps.unionAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MAX, + 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 // DATAREDIS-746 - void testZsetIntersectWithAggregate() { + @ParameterizedRedisTest // GH-2042 + @EnabledOnCommand("ZUNION") + void testZsetUnion() { K key1 = keyFactory.instance(); K key2 = keyFactory.instance(); @@ -450,13 +568,33 @@ void testZsetIntersectWithAggregate() { zSetOps.add(key1, value2, 2.0); zSetOps.add(key2, value2, 3.0); - zSetOps.intersectAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MIN); + assertThat(zSetOps.union(key1, Collections.singletonList(key2))).containsOnly(value1, value2); + } + + @ParameterizedRedisTest // DATAREDIS-746, GH-2042 + @EnabledOnCommand("ZUNION") + void testZsetUnionWithAggregate() { + + 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.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)); } @ParameterizedRedisTest // DATAREDIS-746 - void testZsetIntersectWithAggregateWeights() { + void testZsetUnionWithAggregateWeights() { K key1 = keyFactory.instance(); K key2 = keyFactory.instance(); @@ -465,7 +603,7 @@ void testZsetIntersectWithAggregateWeights() { zSetOps.add(key1, value1, 4.0); zSetOps.add(key2, value1, 3.0); - zSetOps.intersectAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MAX, + zSetOps.unionAndStore(key1, Collections.singletonList(key2), key1, RedisZSetCommands.Aggregate.MAX, Weights.of(1, 2)); assertThat(zSetOps.score(key1, value1)).isCloseTo(6.0, offset(0.1)); diff --git a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTestIntegration.java b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTestIntegration.java index ee42ccb61c..f880278099 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTestIntegration.java +++ b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTestIntegration.java @@ -23,6 +23,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.assertj.core.data.Offset; import org.junit.jupiter.api.BeforeEach; @@ -35,8 +36,10 @@ import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.core.BoundZSetOperations; import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.DefaultTypedTuple; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; /** @@ -119,6 +122,38 @@ void testFirst() { assertThat(zSet.first()).isEqualTo(t1); } + @ParameterizedRedisTest + @EnabledOnCommand("ZPOPMIN") + void testPopFirst() { + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + + zSet.add(t1, 3); + zSet.add(t2, 4); + zSet.add(t3, 5); + + assertThat(zSet.popFirst()).isEqualTo(t1); + assertThat(zSet).hasSize(2); + } + + @ParameterizedRedisTest + @EnabledOnCommand("ZPOPMIN") + void testPopFirstWithTimeout() { + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + + zSet.add(t1, 3); + zSet.add(t2, 4); + zSet.add(t3, 5); + + assertThat(zSet.popFirst(1, TimeUnit.SECONDS)).isEqualTo(t1); + assertThat(zSet).hasSize(2); + } + @ParameterizedRedisTest void testFirstException() { assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> zSet.first()); @@ -126,6 +161,7 @@ void testFirstException() { @ParameterizedRedisTest void testLast() { + T t1 = getT(); T t2 = getT(); T t3 = getT(); @@ -138,6 +174,38 @@ void testLast() { assertThat(zSet.last()).isEqualTo(t3); } + @ParameterizedRedisTest + @EnabledOnCommand("ZPOPMAX") + void testPopLast() { + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + + zSet.add(t1, 3); + zSet.add(t2, 4); + zSet.add(t3, 5); + + assertThat(zSet.popLast()).isEqualTo(t3); + assertThat(zSet).hasSize(2); + } + + @ParameterizedRedisTest + @EnabledOnCommand("ZPOPMAX") + void testPopLastWithTimeout() { + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + + zSet.add(t1, 3); + zSet.add(t2, 4); + zSet.add(t3, 5); + + assertThat(zSet.popLast(1, TimeUnit.SECONDS)).isEqualTo(t3); + assertThat(zSet).hasSize(2); + } + @ParameterizedRedisTest void testLastException() { assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> zSet.last()); @@ -236,35 +304,6 @@ private RedisZSet createZSetFor(String key) { return new DefaultRedisZSet<>((BoundZSetOperations) zSet.getOperations().boundZSetOps(key)); } - @ParameterizedRedisTest - void testIntersectAndStore() { - - RedisZSet interSet1 = createZSetFor("test:zset:inter1"); - RedisZSet interSet2 = createZSetFor("test:zset:inter"); - - T t1 = getT(); - T t2 = getT(); - T t3 = getT(); - T t4 = getT(); - - zSet.add(t1, 1); - zSet.add(t2, 2); - zSet.add(t3, 3); - - interSet1.add(t2, 2); - interSet1.add(t4, 3); - interSet2.add(t2, 2); - interSet2.add(t3, 3); - - String resultName = "test:zset:inter:result:1"; - RedisZSet inter = zSet.intersectAndStore(Arrays.asList(interSet1, interSet2), resultName); - - assertThat(inter).hasSize(1); - assertThat(inter).contains(t2); - assertThat(inter.score(t2)).isEqualTo(Double.valueOf(6)); - assertThat(inter.getKey()).isEqualTo(resultName); - } - @ParameterizedRedisTest void testRange() { T t1 = getT(); @@ -573,6 +612,136 @@ void testRemoveByScore() { assertThat(iterator.next()).isEqualTo(t4); } + @ParameterizedRedisTest // GH-2041 + @EnabledOnCommand("ZDIFF") + void testDifference() { + + RedisZSet set1 = createZSetFor("test:zset:set1"); + RedisZSet set2 = createZSetFor("test:zset:set2"); + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + T t4 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + + set1.add(t2, 2); + set1.add(t4, 3); + set2.add(t2, 2); + set2.add(t3, 3); + + assertThat(zSet.difference(Arrays.asList(set1, set2))).containsOnly(t1); + assertThat(zSet.differenceWithScores(Arrays.asList(set1, set2))).containsOnly(new DefaultTypedTuple<>(t1, 1d)); + } + + @ParameterizedRedisTest // GH-2041 + void testDifferenceAndStore() { + + RedisZSet set1 = createZSetFor("test:zset:set1"); + RedisZSet set2 = createZSetFor("test:zset:set2"); + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + T t4 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + + set1.add(t2, 2); + set1.add(t4, 3); + set2.add(t2, 2); + set2.add(t3, 3); + + String resultName = "test:zset:inter:result:1"; + RedisZSet diff = zSet.differenceAndStore(Arrays.asList(set1, set2), resultName); + + assertThat(diff).containsOnly(t1); + } + + @ParameterizedRedisTest // GH-2042 + @EnabledOnCommand("ZINTER") + void testIntersect() { + + RedisZSet interSet1 = createZSetFor("test:zset:inter1"); + RedisZSet interSet2 = createZSetFor("test:zset:inter"); + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + T t4 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + + interSet1.add(t2, 2); + interSet1.add(t4, 3); + interSet2.add(t2, 2); + interSet2.add(t3, 3); + + assertThat(zSet.intersect(Arrays.asList(interSet1, interSet2))).containsOnly(t2); + assertThat(zSet.intersectWithScores(Arrays.asList(interSet1, interSet2))) + .containsOnly(new DefaultTypedTuple<>(t2, 6d)); + } + + @ParameterizedRedisTest + void testIntersectAndStore() { + + RedisZSet interSet1 = createZSetFor("test:zset:inter1"); + RedisZSet interSet2 = createZSetFor("test:zset:inter"); + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + T t4 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + + interSet1.add(t2, 2); + interSet1.add(t4, 3); + interSet2.add(t2, 2); + interSet2.add(t3, 3); + + String resultName = "test:zset:inter:result:1"; + RedisZSet inter = zSet.intersectAndStore(Arrays.asList(interSet1, interSet2), resultName); + + assertThat(inter).hasSize(1); + assertThat(inter).contains(t2); + assertThat(inter.score(t2)).isEqualTo(Double.valueOf(6)); + assertThat(inter.getKey()).isEqualTo(resultName); + } + + @ParameterizedRedisTest // GH-2042 + @EnabledOnCommand("ZUNION") + void testUnion() { + + RedisZSet set1 = createZSetFor("test:zset:union1"); + RedisZSet set2 = createZSetFor("test:zset:union2"); + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + T t4 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + + set1.add(t2, 2); + set1.add(t4, 3); + set2.add(t2, 2); + set2.add(t3, 3); + + assertThat(zSet.union(Arrays.asList(set1, set2))).contains(t1, t2, t3, t4); + } + @SuppressWarnings("unchecked") @ParameterizedRedisTest void testUnionAndStore() { diff --git a/src/test/java/org/springframework/data/redis/support/collections/RedisZSetIntegrationTests.java b/src/test/java/org/springframework/data/redis/support/collections/RedisZSetIntegrationTests.java index 86c87d8b12..b76406eee5 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/RedisZSetIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/support/collections/RedisZSetIntegrationTests.java @@ -36,7 +36,7 @@ public RedisZSetIntegrationTests(ObjectFactory factory, RedisTemplate te } RedisStore copyStore(RedisStore store) { - return new DefaultRedisZSet(store.getKey().toString(), store.getOperations()); + return RedisZSet.create(store.getKey(), store.getOperations()); } AbstractRedisCollection createCollection() {