diff --git a/pom.xml b/pom.xml index b20e7c9dcc..563f362e43 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-redis - 2.5.0-SNAPSHOT + 2.5.0-GH-1794-SNAPSHOT Spring Data Redis 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 90976449bb..277341d3ed 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -1322,20 +1322,20 @@ public void watch(byte[]... keys) { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], double, byte[]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], double, byte[], org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs) */ @Override - public Boolean zAdd(byte[] key, double score, byte[] value) { - return convertAndReturn(delegate.zAdd(key, score, value), Converters.identityConverter()); + public Boolean zAdd(byte[] key, double score, byte[] value, ZAddArgs args) { + return convertAndReturn(delegate.zAdd(key, score, value, args), Converters.identityConverter()); } /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], java.util.Set) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], java.util.Set, org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs) */ @Override - public Long zAdd(byte[] key, Set tuples) { - return convertAndReturn(delegate.zAdd(key, tuples), Converters.identityConverter()); + public Long zAdd(byte[] key, Set tuples, ZAddArgs args) { + return convertAndReturn(delegate.zAdd(key, tuples, args), Converters.identityConverter()); } /* @@ -2686,20 +2686,20 @@ public Long touch(String... keys) { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.StringRedisConnection#zAdd(java.lang.String, double, java.lang.String) + * @see org.springframework.data.redis.connection.StringRedisConnection#zAdd(java.lang.String, double, java.lang.String, org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs) */ @Override - public Boolean zAdd(String key, double score, String value) { - return zAdd(serialize(key), score, serialize(value)); + public Boolean zAdd(String key, double score, String value, ZAddArgs args) { + return zAdd(serialize(key), score, serialize(value), args); } /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.StringRedisConnection#zAdd(java.lang.String, java.util.Set) + * @see org.springframework.data.redis.connection.StringRedisConnection#zAdd(java.lang.String, java.util.Set, org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs) */ @Override - public Long zAdd(String key, Set tuples) { - return zAdd(serialize(key), stringTupleToTuple.convert(tuples)); + public Long zAdd(String key, Set tuples, ZAddArgs args) { + return zAdd(serialize(key), stringTupleToTuple.convert(tuples), args); } /* 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 41c50d7f18..2b09e98a6f 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -873,15 +873,15 @@ default Cursor sScan(byte[] key, ScanOptions options) { /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated - default Boolean zAdd(byte[] key, double score, byte[] value) { - return zSetCommands().zAdd(key, score, value); + default Boolean zAdd(byte[] key, double score, byte[] value, ZAddArgs args) { + return zSetCommands().zAdd(key, score, value, args); } /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated - default Long zAdd(byte[] key, Set tuples) { - return zSetCommands().zAdd(key, tuples); + default Long zAdd(byte[] key, Set tuples, ZAddArgs args) { + return zSetCommands().zAdd(key, tuples, args); } /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ 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 27e24253cf..ed307120c2 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java @@ -63,9 +63,11 @@ class ZAddCommand extends KeyCommand { private final boolean upsert; private final boolean returnTotalChanged; private final boolean incr; + private final boolean gt; + private final boolean lt; private ZAddCommand(@Nullable ByteBuffer key, List tuples, boolean upsert, boolean returnTotalChanged, - boolean incr) { + boolean incr, boolean gt, boolean lt) { super(key); @@ -73,6 +75,8 @@ private ZAddCommand(@Nullable ByteBuffer key, List tuples, boolean upsert this.upsert = upsert; this.returnTotalChanged = returnTotalChanged; this.incr = incr; + this.gt = gt; + this.lt = lt; } /** @@ -98,7 +102,7 @@ public static ZAddCommand tuples(Collection tuples) { Assert.notNull(tuples, "Tuples must not be null!"); - return new ZAddCommand(null, new ArrayList<>(tuples), false, false, false); + return new ZAddCommand(null, new ArrayList<>(tuples), false, false, false, false, false); } /** @@ -111,7 +115,7 @@ public ZAddCommand to(ByteBuffer key) { Assert.notNull(key, "Key must not be null!"); - return new ZAddCommand(key, tuples, upsert, returnTotalChanged, incr); + return new ZAddCommand(key, tuples, upsert, returnTotalChanged, incr, gt, lt); } /** @@ -121,7 +125,7 @@ public ZAddCommand to(ByteBuffer key) { * @return a new {@link ZAddCommand} with {@literal xx} applied. */ public ZAddCommand xx() { - return new ZAddCommand(getKey(), tuples, false, returnTotalChanged, incr); + return new ZAddCommand(getKey(), tuples, false, returnTotalChanged, incr, gt, lt); } /** @@ -131,7 +135,7 @@ public ZAddCommand xx() { * @return a new {@link ZAddCommand} with {@literal nx} applied. */ public ZAddCommand nx() { - return new ZAddCommand(getKey(), tuples, true, returnTotalChanged, incr); + return new ZAddCommand(getKey(), tuples, true, returnTotalChanged, incr, gt, lt); } /** @@ -141,7 +145,7 @@ public ZAddCommand nx() { * @return a new {@link ZAddCommand} with {@literal ch} applied. */ public ZAddCommand ch() { - return new ZAddCommand(getKey(), tuples, upsert, true, incr); + return new ZAddCommand(getKey(), tuples, upsert, true, incr, gt, lt); } /** @@ -151,7 +155,29 @@ public ZAddCommand ch() { * @return a new {@link ZAddCommand} with {@literal incr} applied. */ public ZAddCommand incr() { - return new ZAddCommand(getKey(), tuples, upsert, upsert, true); + return new ZAddCommand(getKey(), tuples, upsert, upsert, true, gt, lt); + } + + /** + * 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 + */ + public ZAddCommand gt() { + return new ZAddCommand(getKey(), tuples, upsert, upsert, incr, true, lt); + } + + /** + * 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 + */ + public ZAddCommand lt() { + return new ZAddCommand(getKey(), tuples, upsert, upsert, incr, gt, true); } /** @@ -175,6 +201,23 @@ public boolean isIncr() { return incr; } + /** + * + * @return {@literal true} if {@literal GT} is set. + * @since 2.5 + */ + public boolean isGt() { + return gt; + } + + /** + * @return {@literal true} if {@literal LT} is set. + * @since 2.5 + */ + public boolean isLt() { + return lt; + } + /** * @return */ 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 d26e5af2f6..bb48bc551f 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.DoubleUnaryOperator; @@ -396,6 +397,171 @@ public static Limit unlimited() { } } + /** + * {@code ZADD} specific arguments.
+ * Looking of the {@code INCR} flag? Use the {@code ZINCRBY} operation instead. + * + * @since 2.3 + * @see Redis Documentation: ZADD + */ + class ZAddArgs { + + private static final ZAddArgs NONE = new ZAddArgs(Collections.emptySet()); + + private final Set flags; + + private ZAddArgs(Set flags) { + this.flags = flags; + } + + /** + * @return immutable {@link ZAddArgs}. + */ + public static ZAddArgs none() { + return NONE; + } + + /** + * @return new instance of {@link ZAddArgs} without any flags set. + */ + public static ZAddArgs empty() { + return new ZAddArgs(new LinkedHashSet<>(5)); + } + + /** + * @return new instance of {@link ZAddArgs} without {@link Flag#NX} set. + */ + public static ZAddArgs ifNotExists() { + return empty().nx(); + } + + /** + * @return new instance of {@link ZAddArgs} without {@link Flag#NX} set. + */ + public static ZAddArgs ifExists() { + return empty().xx(); + } + + /** + * Only update elements that already exist. + * + * @return this. + */ + public ZAddArgs nx() { + + flags.add(Flag.NX); + return this; + } + + /** + * Don't update already existing elements. + * + * @return this. + */ + public ZAddArgs xx() { + + flags.add(Flag.XX); + return this; + } + + /** + * Only update existing elements if the new score is less than the current score. + * + * @return this. + */ + public ZAddArgs lt() { + + flags.add(Flag.LT); + return this; + } + + /** + * Only update existing elements if the new score is greater than the current score. + * + * @return this. + */ + public ZAddArgs gt() { + + flags.add(Flag.GT); + return this; + } + + /** + * Only update elements that already exist. + * + * @return this. + */ + public ZAddArgs ch() { + + flags.add(Flag.CH); + return this; + } + + /** + * Only update elements that already exist. + * + * @return this. + */ + public boolean contains(Flag flag) { + return flags.contains(flag); + } + + /** + * @return {@literal true} if no flags set. + */ + public boolean isEmpty() { + return !flags.isEmpty(); + } + + @Override + public boolean equals(Object o) { + + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ZAddArgs zAddArgs = (ZAddArgs) o; + + return ObjectUtils.nullSafeEquals(flags, zAddArgs.flags); + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(flags); + } + + public enum Flag { + + /** + * Only update elements that already exist. + */ + XX, + + /** + * Don't update already existing elements. + */ + NX, + + /** + * Only update existing elements if the new score is greater than the current score. + */ + GT, + + /** + * Only update existing elements if the new score is less than the current score. + */ + LT, + + /** + * Modify the return value from the number of new elements added, to the total number of elements changed. + */ + CH + } + } + /** * Add {@code value} to a sorted set at {@code key}, or update its {@code score} if it already exists. * @@ -406,7 +572,24 @@ public static Limit unlimited() { * @see Redis Documentation: ZADD */ @Nullable - Boolean zAdd(byte[] key, double score, byte[] value); + default Boolean zAdd(byte[] key, double score, byte[] value) { + return zAdd(key, score, value, ZAddArgs.none()); + } + + /** + * Add {@code value} to a sorted set at {@code key}, or update its {@code score} depending on the given + * {@link ZAddArgs args}. + * + * @param key must not be {@literal null}. + * @param score the score. + * @param value the value. + * @param args must not be {@literal null} use {@link ZAddArgs#none()} instead. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZADD + */ + @Nullable + Boolean zAdd(byte[] key, double score, byte[] value, ZAddArgs args); /** * Add {@code tuples} to a sorted set at {@code key}, or update its {@code score} if it already exists. @@ -417,7 +600,22 @@ public static Limit unlimited() { * @see Redis Documentation: ZADD */ @Nullable - Long zAdd(byte[] key, Set tuples); + default Long zAdd(byte[] key, Set tuples) { + return zAdd(key, tuples, ZAddArgs.none()); + } + + /** + * Add {@code tuples} to a sorted set at {@code key}, or update its {@code score} depending on the given + * {@link ZAddArgs args}. + * + * @param key must not be {@literal null}. + * @param tuples must not be {@literal null}. + * @param args must not be {@literal null} use {@link ZAddArgs#none()} instead. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZADD + */ + Long zAdd(byte[] key, Set tuples, ZAddArgs args); /** * Remove {@code values} from sorted set. Return number of removed elements. 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 8633ea3a57..b5bd7193e8 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -1102,7 +1102,24 @@ default Long lPos(String key, String element) { * @see Redis Documentation: ZADD * @see RedisZSetCommands#zAdd(byte[], double, byte[]) */ - Boolean zAdd(String key, double score, String value); + default Boolean zAdd(String key, double score, String value) { + return zAdd(key, score, value, ZAddArgs.none()); + } + + /** + * Add the {@code value} to a sorted set at {@code key}, or update its {@code score} depending on the given + * {@link ZAddArgs args}. + * + * @param key must not be {@literal null}. + * @param score must not be {@literal null}. + * @param value must not be {@literal null}. + * @param args must not be {@literal null} use {@link ZAddArgs#none()} instead. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZADD + * @see RedisZSetCommands#zAdd(byte[], double, byte[], ZAddArgs) + */ + Boolean zAdd(String key, double score, String value, ZAddArgs args); /** * Add {@code tuples} to a sorted set at {@code key}, or update its {@code score} if it already exists. @@ -1113,7 +1130,24 @@ default Long lPos(String key, String element) { * @see Redis Documentation: ZADD * @see RedisZSetCommands#zAdd(byte[], Set) */ - Long zAdd(String key, Set tuples); + default Long zAdd(String key, Set tuples) { + return zAdd(key, tuples, ZAddArgs.none()); + } + + /** + * Add {@code tuples} to a sorted set at {@code key}, or update its {@code score} depending on the given + * {@link ZAddArgs args}. + * + * @param key must not be {@literal null}. + * @param tuples must not be {@literal null}. + * @param args must not be {@literal null} use {@link ZAddArgs#none()} instead. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZADD + * @see RedisZSetCommands#zAdd(byte[], Set, ZAddArgs) + */ + @Nullable + Long zAdd(String key, Set tuples, ZAddArgs args); /** * Remove {@code values} from sorted set. Return number of removed elements. 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 de3470c183..376f7c8b40 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 @@ -54,13 +54,13 @@ class JedisClusterZSetCommands implements RedisZSetCommands { * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], double, byte[]) */ @Override - public Boolean zAdd(byte[] key, double score, byte[] value) { + public Boolean zAdd(byte[] key, double score, byte[] value, ZAddArgs args) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(value, "Value must not be null!"); try { - return JedisConverters.toBoolean(connection.getCluster().zadd(key, score, value)); + return JedisConverters.toBoolean(connection.getCluster().zadd(key, score, value, JedisConverters.toZAddParams(args))); } catch (Exception ex) { throw convertJedisAccessException(ex); } @@ -68,16 +68,16 @@ public Boolean zAdd(byte[] key, double score, byte[] value) { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], java.util.Set) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], java.util.Set, org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs) */ @Override - public Long zAdd(byte[] key, Set tuples) { + public Long zAdd(byte[] key, Set tuples, ZAddArgs args) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(tuples, "Tuples must not be null!"); try { - return connection.getCluster().zadd(key, JedisConverters.toTupleMap(tuples)); + return connection.getCluster().zadd(key, JedisConverters.toTupleMap(tuples), JedisConverters.toZAddParams(args)); } catch (Exception ex) { throw convertJedisAccessException(ex); } 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 5e34e143e5..1346279dc4 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 @@ -24,6 +24,7 @@ import redis.clients.jedis.SortingParams; import redis.clients.jedis.params.GeoRadiusParam; import redis.clients.jedis.params.SetParams; +import redis.clients.jedis.params.ZAddParams; import redis.clients.jedis.util.SafeEncoder; import java.nio.ByteBuffer; @@ -61,6 +62,7 @@ import org.springframework.data.redis.connection.RedisStringCommands.SetOption; import org.springframework.data.redis.connection.RedisZSetCommands.Range.Boundary; import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; +import org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.SortParameters.Order; import org.springframework.data.redis.connection.SortParameters.Range; @@ -651,6 +653,42 @@ public static GeoUnit toGeoUnit(Metric metric) { return ObjectUtils.caseInsensitiveValueOf(GeoUnit.values(), metricToUse.getAbbreviation()); } + /** + * Convert {@link ZAddArgs} to {@link ZAddParams}. + * + * @param source must not be {@literal null}. + * @return new instance of {@link ZAddParams}. + * @since 2.5 + */ + static ZAddParams toZAddParams(ZAddArgs source) { + + if (!source.isEmpty()) { + return new ZAddParams(); + } + + ZAddParams target = new ZAddParams() { + + { + if (source.contains(ZAddArgs.Flag.GT)) { + addParam("gt"); + } + if (source.contains(ZAddArgs.Flag.LT)) { + addParam("lt"); + } + } + }; + + if (source.contains(ZAddArgs.Flag.XX)) { + target.xx(); + } + if (source.contains(ZAddArgs.Flag.NX)) { + target.nx(); + } + if (source.contains(ZAddArgs.Flag.CH)) { + target.ch(); + } + return target; + } /** * Convert {@link GeoRadiusCommandArgs} into {@link GeoRadiusParam}. 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 46c3efd722..2f6b4f67e7 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,12 +21,14 @@ 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.Set; 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; @@ -53,27 +55,27 @@ class JedisZSetCommands implements RedisZSetCommands { * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], double, byte[]) */ @Override - public Boolean zAdd(byte[] key, double score, byte[] value) { + public Boolean zAdd(byte[] key, double score, byte[] value, ZAddArgs args) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(value, "Value must not be null!"); - return connection.invoke().from(BinaryJedis::zadd, MultiKeyPipelineBase::zadd, key, score, value) + return connection.invoke().from(BinaryJedis::zadd, MultiKeyPipelineBase::zadd, key, score, value, JedisConverters.toZAddParams(args)) .get(JedisConverters::toBoolean); } /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], java.util.Set) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], java.util.Set, org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs) */ @Override - public Long zAdd(byte[] key, Set tuples) { + public Long zAdd(byte[] key, Set tuples, ZAddArgs args) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(tuples, "Tuples must not be null!"); return connection.invoke().just(BinaryJedis::zadd, MultiKeyPipelineBase::zadd, key, - JedisConverters.toTupleMap(tuples)); + JedisConverters.toTupleMap(tuples), JedisConverters.toZAddParams(args)); } /* 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 1cc6ae1c3f..8696c408f0 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 @@ -102,6 +102,12 @@ public Flux> zAdd(Publisher co } else { args = ZAddArgs.Builder.xx(); } + if(command.isGt()) { + args = args == null ? ZAddArgs.Builder.gt() : args.gt(); + } + if(command.isLt()) { + args = args == null ? ZAddArgs.Builder.lt() : args.lt(); + } } ScoredValue[] values = (ScoredValue[]) command.getTuples().stream() 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 9d011364ce..44ff5a0376 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 @@ -26,6 +26,7 @@ import java.util.Set; import org.springframework.data.redis.connection.RedisZSetCommands; +import org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs.Flag; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.KeyBoundCursor; @@ -50,29 +51,29 @@ class LettuceZSetCommands implements RedisZSetCommands { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], double, byte[]) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], double, byte[], org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs) */ @Override - public Boolean zAdd(byte[] key, double score, byte[] value) { + public Boolean zAdd(byte[] key, double score, byte[] value, ZAddArgs args) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(value, "Value must not be null!"); - return connection.invoke().from(RedisSortedSetAsyncCommands::zadd, key, score, value) + return connection.invoke().from(RedisSortedSetAsyncCommands::zadd, key, LettuceZSetCommands.toZAddArgs(args), score, value) .get(LettuceConverters.longToBoolean()); } /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], java.util.Set) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zAdd(byte[], java.util.Set, org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs) */ @Override - public Long zAdd(byte[] key, Set tuples) { + public Long zAdd(byte[] key, Set tuples, ZAddArgs args) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(tuples, "Tuples must not be null!"); - return connection.invoke().just(RedisSortedSetAsyncCommands::zadd, key, + return connection.invoke().just(RedisSortedSetAsyncCommands::zadd, key, LettuceZSetCommands.toZAddArgs(args), LettuceConverters.toObjects(tuples).toArray()); } @@ -568,4 +569,37 @@ private static ZStoreArgs zStoreArgs(@Nullable Aggregate aggregate, Weights weig return args; } + /** + * Convert {@link ZAddArgs} to {@link io.lettuce.core.ZAddArgs}. + * + * @param source must not be {@literal null}. + * @return never {@literal null}. + * @since 2.5 + */ + private static io.lettuce.core.ZAddArgs toZAddArgs(ZAddArgs source) { + + io.lettuce.core.ZAddArgs target = new io.lettuce.core.ZAddArgs(); + + if(!source.isEmpty()) { + return target; + } + + if(source.contains(Flag.XX)) { + target.xx(); + } + if(source.contains(Flag.NX)) { + target.nx(); + } + if(source.contains(Flag.GT)) { + target.gt(); + } + if(source.contains(Flag.LT)) { + target.lt(); + } + if(source.contains(Flag.CH)) { + target.ch(); + } + return target; + } + } 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 5baa3572ef..8ff3df2eee 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java @@ -48,6 +48,18 @@ public interface BoundZSetOperations extends BoundKeyOperations { @Nullable Boolean add(V value, double score); + /** + * Add {@code value} to a sorted set at {@code key} if it does not already exists. + * + * @param score the score. + * @param value the value. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZADD NX + */ + @Nullable + Boolean addIfAbsent(V value, double score); + /** * Add {@code tuples} to a sorted set at the bound key, or update its {@code score} if it already exists. * @@ -58,6 +70,17 @@ public interface BoundZSetOperations extends BoundKeyOperations { @Nullable Long add(Set> tuples); + /** + * Add {@code tuples} to a sorted set at {@code key} if it does not already exists. + * + * @param tuples must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZADD NX + */ + @Nullable + Long addIfAbsent(Set> tuples); + /** * Remove {@code values} from sorted set. Return number of removed elements. * 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 fc3b5f9b73..f9a4a42bee 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java @@ -60,6 +60,15 @@ public Boolean add(V value, double score) { return ops.add(getKey(), value, score); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#addIfAbsent(java.lang.Object, double) + */ + @Override + public Boolean addIfAbsent(V value, double score) { + return ops.addIfAbsent(getKey(), value, score); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundZSetOperations#add(java.util.Set) @@ -69,6 +78,15 @@ public Long add(Set> tuples) { return ops.add(getKey(), tuples); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#addIfAbsent(java.util.Set) + */ + @Override + public Long addIfAbsent(Set> tuples) { + return ops.addIfAbsent(getKey(), tuples); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundZSetOperations#incrementScore(java.lang.Object, double) 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 1c14991326..5c88953240 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java @@ -24,6 +24,8 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Range; import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; import org.springframework.data.redis.connection.RedisZSetCommands.Weights; +import org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs; +import org.springframework.lang.Nullable; /** * Default implementation of {@link ZSetOperations}. @@ -48,10 +50,31 @@ class DefaultZSetOperations extends AbstractOperations implements ZS */ @Override public Boolean add(K key, V value, double score) { + return add(key, value, score, ZAddArgs.none()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#addIfAbsent(java.lang.Object, java.lang.Object, double) + */ + @Override + public Boolean addIfAbsent(K key, V value, double score) { + return add(key, value, score, ZAddArgs.ifNotExists()); + } + + /** + * @param key must not be {@literal null}. + * @param value must not be {@literal null}. + * @param args never {@literal null}. + * @return can be {@literal null}. + * @since 2.5 + */ + @Nullable + protected Boolean add(K key, V value, double score, ZAddArgs args) { byte[] rawKey = rawKey(key); byte[] rawValue = rawValue(value); - return execute(connection -> connection.zAdd(rawKey, score, rawValue), true); + return execute(connection -> connection.zAdd(rawKey, score, rawValue, args), true); } /* @@ -60,10 +83,31 @@ public Boolean add(K key, V value, double score) { */ @Override public Long add(K key, Set> tuples) { + return add(key, tuples, ZAddArgs.none()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#addIfAbsent(java.lang.Object, java.util.Set) + */ + @Override + public Long addIfAbsent(K key, Set> tuples) { + return add(key, tuples, ZAddArgs.ifNotExists()); + } + + /** + * @param key must not be {@literal null}. + * @param tuples must not be {@literal null}. + * @param args never {@literal null}. + * @return can be {@literal null}. + * @since 2.5 + */ + @Nullable + protected Long add(K key, Set> tuples, ZAddArgs args) { byte[] rawKey = rawKey(key); Set rawValues = rawTupleValues(tuples); - return execute(connection -> connection.zAdd(rawKey, rawValues), true); + return execute(connection -> connection.zAdd(rawKey, rawValues, args), true); } /* 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 4aa5c82634..822b8a47a7 100644 --- a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java @@ -47,6 +47,19 @@ interface TypedTuple extends Comparable> { @Nullable Double getScore(); + + /** + * Create a new {@link TypedTuple}. + * + * @param value must not be {@literal null}. + * @param score can be {@literal null}. + * @param + * @return new instance of {@link TypedTuple}. + * @since 2.5 + */ + static TypedTuple of(V value, @Nullable Double score) { + return new DefaultTypedTuple<>(value, score); + } } /** @@ -61,6 +74,19 @@ interface TypedTuple extends Comparable> { @Nullable Boolean add(K key, V value, double score); + /** + * Add {@code value} to a sorted set at {@code key} if it does not already exists. + * + * @param key must not be {@literal null}. + * @param score the score. + * @param value the value. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZADD NX + */ + @Nullable + Boolean addIfAbsent(K key, V value, double score); + /** * Add {@code tuples} to a sorted set at {@code key}, or update its {@code score} if it already exists. * @@ -72,6 +98,18 @@ interface TypedTuple extends Comparable> { @Nullable Long add(K key, Set> tuples); + /** + * Add {@code tuples} to a sorted set at {@code key} if it does not already exists. + * + * @param key must not be {@literal null}. + * @param tuples must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZADD NX + */ + @Nullable + Long addIfAbsent(K key, Set> tuples); + /** * Remove {@code values} from sorted set. Return number of removed elements. * 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 4fda95ea62..d9b9add8ad 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 @@ -288,6 +288,18 @@ public boolean add(E e, double score) { return result; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#addIfAbsent(java.lang.Object, double) + */ + @Override + public boolean addIfAbsent(E e, double score) { + + Boolean result = boundZSetOps.addIfAbsent(e, score); + checkResult(result); + return result; + } + /* * (non-Javadoc) * @see java.util.AbstractCollection#clear() 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 e6af859e57..86fc8646ef 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 @@ -144,6 +144,27 @@ default Set reverseRangeByLex(Range range) { */ boolean add(E e); + /** + * Adds an element to the set using the {@link #getDefaultScore() default score} if the element does not already exists. + * + * @param e element to add + * @return true if a new element was added, false otherwise (only the score has been updated) + * @since 2.5 + */ + default boolean addIfAbsent(E e) { + return addIfAbsent(e, getDefaultScore()); + } + + /** + * Adds an element to the set with the given score if the element does not already exists. + * + * @param e element to add + * @param score element score + * @return true if a new element was added, false otherwise (only the score has been updated) + * @since 2.5 + */ + boolean addIfAbsent(E e, double score); + /** * Count number of elements within sorted set with value between {@code Range#min} and {@code Range#max} applying * lexicographical ordering. 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 5899c66362..9b8a179d79 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -37,7 +37,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.domain.Range.Bound; @@ -57,6 +56,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Limit; import org.springframework.data.redis.connection.RedisZSetCommands.Range; import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; +import org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs; import org.springframework.data.redis.connection.SortParameters.Order; import org.springframework.data.redis.connection.StringRedisConnection.StringTuple; import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding; @@ -1312,8 +1312,7 @@ void testLSet() { actual.add(connection.rPush("PopList", "world")); connection.lSet("PopList", 1, "cruel"); actual.add(connection.lRange("PopList", 0, -1)); - verifyResults( - Arrays.asList(1L, 2L, 3L, Arrays.asList("hello", "cruel", "world"))); + verifyResults(Arrays.asList(1L, 2L, 3L, Arrays.asList("hello", "cruel", "world"))); } @Test @@ -1489,8 +1488,7 @@ void testSAddMultiple() { actual.add(connection.sAdd("myset", "foo", "bar")); actual.add(connection.sAdd("myset", "baz")); actual.add(connection.sMembers("myset")); - verifyResults( - Arrays.asList(new Object[] { 2L, 1L, new HashSet<>(Arrays.asList("foo", "bar", "baz")) })); + verifyResults(Arrays.asList(new Object[] { 2L, 1L, new HashSet<>(Arrays.asList("foo", "bar", "baz")) })); } @Test @@ -1562,8 +1560,7 @@ void testSPop() { actual.add(connection.sAdd("myset", "foo")); actual.add(connection.sAdd("myset", "bar")); actual.add(connection.sPop("myset")); - assertThat(new HashSet<>(Arrays.asList("foo", "bar")).contains((String) getResults().get(2))) - .isTrue(); + assertThat(new HashSet<>(Arrays.asList("foo", "bar")).contains((String) getResults().get(2))).isTrue(); } @Test // DATAREDIS-688 @@ -1643,8 +1640,7 @@ void testSUnion() { actual.add(connection.sAdd("otherset", "bar")); actual.add(connection.sAdd("otherset", "baz")); actual.add(connection.sUnion("myset", "otherset")); - verifyResults(Arrays - .asList(new Object[] { 1L, 1L, 1L, 1L, new HashSet<>(Arrays.asList("foo", "bar", "baz")) })); + verifyResults(Arrays.asList(new Object[] { 1L, 1L, 1L, 1L, new HashSet<>(Arrays.asList("foo", "bar", "baz")) })); } @Test @@ -1655,8 +1651,8 @@ void testSUnionStore() { actual.add(connection.sAdd("otherset", "baz")); actual.add(connection.sUnionStore("thirdset", "myset", "otherset")); actual.add(connection.sMembers("thirdset")); - verifyResults(Arrays.asList( - new Object[] { 1L, 1L, 1L, 1L, 3L, new HashSet<>(Arrays.asList("foo", "bar", "baz")) })); + verifyResults( + Arrays.asList(new Object[] { 1L, 1L, 1L, 1L, 3L, new HashSet<>(Arrays.asList("foo", "bar", "baz")) })); } // ZSet @@ -1666,8 +1662,7 @@ void testZAddAndZRange() { actual.add(connection.zAdd("myset", 2, "Bob")); actual.add(connection.zAdd("myset", 1, "James")); actual.add(connection.zRange("myset", 0, -1)); - verifyResults(Arrays - .asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("James", "Bob")) })); + verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("James", "Bob")) })); } @Test @@ -1680,8 +1675,52 @@ void testZAddMultiple() { actual.add(connection.zAdd("myset", strTuples)); actual.add(connection.zAdd("myset".getBytes(), tuples)); actual.add(connection.zRange("myset", 0, -1)); - verifyResults(Arrays - .asList(new Object[] { 2L, 1L, new LinkedHashSet<>(Arrays.asList("James", "Bob", "Joe")) })); + verifyResults(Arrays.asList(new Object[] { 2L, 1L, new LinkedHashSet<>(Arrays.asList("James", "Bob", "Joe")) })); + } + + @Test // GH-1794 + void zAddNX() { + + actual.add(connection.zAdd("myset", 1, "Bob", ZAddArgs.ifNotExists())); + actual.add(connection.zAdd("myset", 2, "Bob", ZAddArgs.ifNotExists())); + + verifyResults(Arrays.asList(true, false)); + } + + @Test // GH-1794 + void zAddXX() { + + actual.add(connection.zAdd("myset", 1, "Bob", ZAddArgs.ifExists())); + actual.add(connection.zAdd("myset", 1, "Bob")); + actual.add(connection.zAdd("myset", 2, "Bob", ZAddArgs.ifExists())); + + verifyResults(Arrays.asList(false, true, false)); + } + + @Test // GH-1794 + void testZAddMultipleNX() { + + actual.add(connection.zAdd("myset", 1, "James")); + + Set strTuples = new HashSet<>(); + strTuples.add(new DefaultStringTuple("Bob".getBytes(), "Bob", 2.0)); + strTuples.add(new DefaultStringTuple("James".getBytes(), "James", 1.0)); + + actual.add(connection.zAdd("myset", strTuples, ZAddArgs.ifNotExists())); + verifyResults(Arrays.asList(true, 1L)); + } + + @Test // GH-1794 + void testZAddMultipleXX() { + + actual.add(connection.zAdd("myset", 1, "James")); + + Set strTuples = new HashSet<>(); + strTuples.add(new DefaultStringTuple("Bob".getBytes(), "Bob", 2.0)); + strTuples.add(new DefaultStringTuple("James".getBytes(), "James", 2.0)); + + actual.add(connection.zAdd("myset", strTuples, ZAddArgs.ifNotExists())); + verifyResults(Arrays.asList(true, 1L)); } @Test @@ -1781,8 +1820,7 @@ void testZRangeByScore() { actual.add(connection.zAdd("myset", 2, "Bob")); actual.add(connection.zAdd("myset", 1, "James")); actual.add(connection.zRangeByScore("myset", 1, 1)); - verifyResults( - Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("James")) })); + verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("James")) })); } @Test @@ -1790,8 +1828,7 @@ void testZRangeByScoreOffsetCount() { actual.add(connection.zAdd("myset", 2, "Bob")); actual.add(connection.zAdd("myset", 1, "James")); actual.add(connection.zRangeByScore("myset", 1d, 3d, 1, -1)); - verifyResults( - Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("Bob")) })); + verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("Bob")) })); } @Test @@ -1799,8 +1836,8 @@ void testZRangeByScoreWithScores() { actual.add(connection.zAdd("myset", 2, "Bob")); actual.add(connection.zAdd("myset", 1, "James")); actual.add(connection.zRangeByScoreWithScores("myset", 2d, 5d)); - verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>( - Arrays.asList(new DefaultStringTuple("Bob".getBytes(), "Bob", 2d))) })); + verifyResults(Arrays.asList(new Object[] { true, true, + new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("Bob".getBytes(), "Bob", 2d))) })); } @Test @@ -1808,8 +1845,8 @@ void testZRangeByScoreWithScoresOffsetCount() { actual.add(connection.zAdd("myset", 2, "Bob")); actual.add(connection.zAdd("myset", 1, "James")); actual.add(connection.zRangeByScoreWithScores("myset", 1d, 5d, 0, 1)); - verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>( - Arrays.asList(new DefaultStringTuple("James".getBytes(), "James", 1d))) })); + verifyResults(Arrays.asList(new Object[] { true, true, + new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("James".getBytes(), "James", 1d))) })); } @Test @@ -1817,8 +1854,7 @@ void testZRevRange() { actual.add(connection.zAdd("myset", 2, "Bob")); actual.add(connection.zAdd("myset", 1, "James")); actual.add(connection.zRevRange("myset", 0, -1)); - verifyResults(Arrays - .asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("Bob", "James")) })); + verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("Bob", "James")) })); } @Test @@ -1836,8 +1872,7 @@ void testZRevRangeByScoreOffsetCount() { actual.add(connection.zAdd("myset".getBytes(), 2, "Bob".getBytes())); actual.add(connection.zAdd("myset".getBytes(), 1, "James".getBytes())); actual.add(connection.zRevRangeByScore("myset", 0d, 3d, 0, 5)); - verifyResults(Arrays - .asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("Bob", "James")) })); + verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("Bob", "James")) })); } @Test @@ -1845,8 +1880,7 @@ void testZRevRangeByScore() { actual.add(connection.zAdd("myset".getBytes(), 2, "Bob".getBytes())); actual.add(connection.zAdd("myset".getBytes(), 1, "James".getBytes())); actual.add(connection.zRevRangeByScore("myset", 0d, 3d)); - verifyResults(Arrays - .asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("Bob", "James")) })); + verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("Bob", "James")) })); } @Test @@ -1854,8 +1888,8 @@ void testZRevRangeByScoreWithScoresOffsetCount() { actual.add(connection.zAdd("myset".getBytes(), 2, "Bob".getBytes())); actual.add(connection.zAdd("myset".getBytes(), 1, "James".getBytes())); actual.add(connection.zRevRangeByScoreWithScores("myset", 0d, 3d, 0, 1)); - verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>( - Arrays.asList(new DefaultStringTuple("Bob".getBytes(), "Bob", 2d))) })); + verifyResults(Arrays.asList(new Object[] { true, true, + new LinkedHashSet<>(Arrays.asList(new DefaultStringTuple("Bob".getBytes(), "Bob", 2d))) })); } @Test @@ -1884,8 +1918,7 @@ void testZRem() { actual.add(connection.zAdd("myset", 1, "James")); actual.add(connection.zRem("myset", "James")); actual.add(connection.zRange("myset", 0L, -1L)); - verifyResults( - Arrays.asList(new Object[] { true, true, 1L, new LinkedHashSet<>(Arrays.asList("Bob")) })); + verifyResults(Arrays.asList(new Object[] { true, true, 1L, new LinkedHashSet<>(Arrays.asList("Bob")) })); } @Test @@ -1925,7 +1958,8 @@ void testZRemRangeByLex() { actual.add(connection.zRemRangeByLex("myset", Range.range().gte("alpha").lte("omega"))); actual.add(connection.zRange("myset", 0L, -1L)); - verifyResults(Arrays.asList( true, true, true, true, true, true, true, true, true, true, 6L, new LinkedHashSet<>(Arrays.asList("ALPHA", "aaaa", "zap", "zip")))); + verifyResults(Arrays.asList(true, true, true, true, true, true, true, true, true, true, 6L, + new LinkedHashSet<>(Arrays.asList("ALPHA", "aaaa", "zap", "zip")))); } @Test @@ -1935,8 +1969,7 @@ void testZRemRangeByScore() { actual.add(connection.zRemRangeByScore("myset", 0d, 1d)); actual.add(connection.zRange("myset", 0L, -1L)); verifyResults( - Arrays.asList(new Object[] { true, true, 1L, new LinkedHashSet<>(Collections - .singletonList("Bob")) })); + Arrays.asList(new Object[] { true, true, 1L, new LinkedHashSet<>(Collections.singletonList("Bob")) })); } @Test @@ -1979,8 +2012,8 @@ void testZUnionStoreAggWeights() { actual.add(connection.zAdd("otherset", 4, "James")); 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), + 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))) })); } @@ -2052,8 +2085,7 @@ void testHKeys() { actual.add(connection.hSet("test", "key", "2")); actual.add(connection.hSet("test", "key2", "2")); actual.add(connection.hKeys("test")); - verifyResults( - Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("key", "key2")) })); + verifyResults(Arrays.asList(new Object[] { true, true, new LinkedHashSet<>(Arrays.asList("key", "key2")) })); } @Test 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 b5ac42a5db..0bce55b824 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java @@ -1233,14 +1233,14 @@ public void testType() { @Test public void testZAddBytes() { - doReturn(true).when(nativeConnection).zAdd(fooBytes, 3d, barBytes); + doReturn(true).when(nativeConnection).zAdd(eq(fooBytes), eq(3d), eq(barBytes), any()); actual.add(connection.zAdd(fooBytes, 3d, barBytes)); verifyResults(Collections.singletonList(true)); } @Test public void testZAdd() { - doReturn(true).when(nativeConnection).zAdd(fooBytes, 3d, barBytes); + doReturn(true).when(nativeConnection).zAdd(eq(fooBytes), eq(3d), eq(barBytes), any()); actual.add(connection.zAdd(foo, 3d, bar)); verifyResults(Collections.singletonList(true)); } @@ -1249,7 +1249,7 @@ public void testZAdd() { public void testZAddMultipleBytes() { Set tuples = new HashSet<>(); tuples.add(new DefaultTuple(barBytes, 3.0)); - doReturn(1L).when(nativeConnection).zAdd(fooBytes, tuples); + doReturn(1L).when(nativeConnection).zAdd(eq(fooBytes), eq(tuples), any()); actual.add(connection.zAdd(fooBytes, tuples)); verifyResults(Collections.singletonList(1L)); } @@ -1260,7 +1260,7 @@ public void testZAddMultiple() { tuples.add(new DefaultTuple(barBytes, 3.0)); Set strTuples = new HashSet<>(); strTuples.add(new DefaultStringTuple(barBytes, bar, 3.0)); - doReturn(1L).when(nativeConnection).zAdd(fooBytes, tuples); + doReturn(1L).when(nativeConnection).zAdd(eq(fooBytes), eq(tuples), any()); actual.add(connection.zAdd(foo, strTuples)); verifyResults(Collections.singletonList(1L)); } 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 b5a995fea0..76e5252dea 100644 --- a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java @@ -255,8 +255,8 @@ public Object getNativeConnection() { return delegate.getNativeConnection(); } - public Long zAdd(byte[] key, Set tuples) { - return delegate.zAdd(key, tuples); + public Long zAdd(byte[] key, Set tuples, ZAddArgs args) { + return delegate.zAdd(key, tuples, args); } public void subscribe(MessageListener listener, byte[]... channels) { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java index 926d1138f7..dbc2df4367 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java @@ -17,9 +17,14 @@ import static org.mockito.ArgumentMatchers.*; +import java.util.Collections; +import java.util.Set; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.data.redis.connection.RedisZSetCommands.Range; +import org.springframework.data.redis.connection.RedisZSetCommands.ZAddArgs; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; /** * Unit tests for {@link DefaultZSetOperations}. @@ -46,4 +51,21 @@ void delegatesRemoveRangeByLex() { template.verify().zRemRangeByLex(eq(template.serializeKey("key")), eq(range)); } + + @Test // GH-1794 + void delegatesAddIfAbsent() { + + zSetOperations.addIfAbsent("key", "value", 1D); + + template.verify().zAdd(eq(template.serializeKey("key")), eq(1D), eq(template.serializeKey("value")), + eq(ZAddArgs.ifNotExists())); + } + + @Test // GH-1794 + void delegatesAddIfAbsentForTuples() { + + zSetOperations.addIfAbsent("key", Collections.singleton(TypedTuple.of("value", 1D))); + + template.verify().zAdd(eq(template.serializeKey("key")), any(Set.class), eq(ZAddArgs.ifNotExists())); + } } 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 8e07251ca4..ee42ccb61c 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 @@ -47,6 +47,7 @@ * @author Thomas Darimont * @author Mark Paluch * @author Andrey Shlykov + * @author Christoph Strobl */ public abstract class AbstractRedisZSetTestIntegration extends AbstractRedisCollectionIntegrationTests { @@ -677,4 +678,13 @@ void testScanWorksCorrectly() throws IOException { cursor.close(); } + + @ParameterizedRedisTest // GH-1794 + void testZAddIfAbsentWorks() { + + T t1 = getT(); + + assertThat(zSet.addIfAbsent(t1, 1)).isTrue(); + assertThat(zSet.addIfAbsent(t1, 1)).isFalse(); + } }