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 56da1a9fd..6e28a7f74 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -3639,6 +3639,24 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.zRangeByLex(serialize(key), range, limit), byteSetToStringSet); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRevRangeByLex(java.lang.String, org.springframework.data.redis.connection.RedisZSetCommands.Range, org.springframework.data.redis.connection.RedisZSetCommands.Limit) + */ + @Override + public Set zRevRangeByLex(byte[] key, Range range, Limit limit) { + return convertAndReturn(delegate.zRevRangeByLex(key, range, limit), identityConverter); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zRevRangeByLex(java.lang.String, org.springframework.data.redis.connection.RedisZSetCommands.Range, org.springframework.data.redis.connection.RedisZSetCommands.Limit) + */ + @Override + public Set zRevRangeByLex(String key, Range range, Limit limit) { + return convertAndReturn(delegate.zRevRangeByLex(serialize(key), range, limit), byteSetToStringSet); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisServerCommands#migrate(byte[], org.springframework.data.redis.connection.RedisNode, int, org.springframework.data.redis.connection.RedisServerCommands.MigrateOption) 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 4b66f1fdf..5dbbf56a4 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -953,6 +953,13 @@ public interface DefaultedRedisConnection extends RedisConnection { return zSetCommands().zRangeByLex(key, range, limit); } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Set zRevRangeByLex(byte[] key, Range range, Limit limit) { + return zSetCommands().zRevRangeByLex(key, range, limit); + } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated 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 0819a1051..d0582c2a5 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java @@ -39,6 +39,7 @@ import org.springframework.util.ObjectUtils; * @author Thomas Darimont * @author David Liu * @author Mark Paluch + * @author Andrey Shlykov */ public interface RedisZSetCommands { @@ -1017,4 +1018,45 @@ public interface RedisZSetCommands { @Nullable Set zRangeByLex(byte[] key, Range range, Limit limit); + /** + * Get all the elements in the sorted set at {@literal key} in reversed lexicographical ordering. + * + * @param key must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.4 + * @see Redis Documentation: ZREVRANGEBYLEX + */ + @Nullable + default Set zRevRangeByLex(byte[] key) { + return zRevRangeByLex(key, Range.unbounded()); + } + + /** + * Get all the elements in {@link Range} from the sorted set at {@literal key} in reversed lexicographical ordering. + * + * @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 zRevRangeByLex(byte[] key, Range range) { + return zRevRangeByLex(key, range, Limit.unlimited()); + } + + /** + * Get all the elements in {@link Range} from the sorted set at {@literal key} in reversed lexicographical ordering. Result is + * limited via {@link Limit}. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @param limit must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.4 + * @see Redis Documentation: ZREVRANGEBYLEX + */ + @Nullable + Set zRevRangeByLex(byte[] key, Range range, Limit limit); + } 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 7a016c265..9995f3a1c 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -63,6 +63,7 @@ import org.springframework.util.CollectionUtils; * @author Ninad Divadkar * @author Tugdual Grall * @author Dengliming + * @author Andrey Shlykov * @see RedisCallback * @see RedisSerializer * @see StringRedisTemplate @@ -1499,6 +1500,47 @@ public interface StringRedisConnection extends RedisConnection { */ Set zRangeByLex(String key, Range range, Limit limit); + /** + * Get all the elements in the sorted set at {@literal key} in reversed lexicographical ordering. + * + * @param key must not be {@literal null}. + * @return + * @since 2.4 + * @see Redis Documentation: ZREVRANGEBYLEX + * @see RedisZSetCommands#zRevRangeByLex(byte[]) + */ + default Set zRevRangeByLex(String key) { + return zRevRangeByLex(key, Range.unbounded()); + } + + /** + * Get all the elements in {@link Range} from the sorted set at {@literal key} in reversed lexicographical ordering. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @return + * @since 2.4 + * @see Redis Documentation: ZREVRANGEBYLEX + * @see RedisZSetCommands#zRevRangeByLex(byte[], Range) + */ + default Set zRevRangeByLex(String key, Range range) { + return zRevRangeByLex(key, range, Limit.unlimited()); + } + + /** + * Get all the elements in {@link Range} from the sorted set at {@literal key} in reversed lexicographical ordering. Result is + * limited via {@link Limit}. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @param range can be {@literal null}. + * @return + * @since 2.4 + * @see Redis Documentation: ZREVRANGEBYLEX + * @see RedisZSetCommands#zRevRangeByLex(byte[], Range, Limit) + */ + Set zRevRangeByLex(String key, Range range, Limit limit); + // ------------------------------------------------------------------------- // Methods dealing with Redis Hashes // ------------------------------------------------------------------------- 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 33c33418d..dbb6ab305 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 @@ -35,6 +35,7 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Mark Paluch * @author Clement Ong + * @author Andrey Shlykov * @since 2.0 */ class JedisClusterZSetCommands implements RedisZSetCommands { @@ -324,6 +325,30 @@ class JedisClusterZSetCommands implements RedisZSetCommands { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRevRangeByLex(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range, org.springframework.data.redis.connection.RedisZSetCommands.Limit) + */ + @Override + public Set zRevRangeByLex(byte[] key, Range range, Limit limit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range cannot be null for ZREVRANGEBYLEX."); + Assert.notNull(limit, "Limit must not be null!"); + + byte[] min = JedisConverters.boundaryToBytesForZRangeByLex(range.getMin(), JedisConverters.MINUS_BYTES); + byte[] max = JedisConverters.boundaryToBytesForZRangeByLex(range.getMax(), JedisConverters.PLUS_BYTES); + + try { + if (limit.isUnlimited()) { + return connection.getCluster().zrevrangeByLex(key, min, max); + } + return connection.getCluster().zrevrangeByLex(key, min, max, limit.getOffset(), limit.getCount()); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRangeWithScores(byte[], long, long) 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 2e02fc7c2..b07f8cb62 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 @@ -20,6 +20,7 @@ import redis.clients.jedis.ScanResult; import redis.clients.jedis.ZParams; import java.nio.charset.StandardCharsets; +import java.util.LinkedHashSet; import java.util.Set; import org.springframework.data.redis.connection.RedisZSetCommands; @@ -27,7 +28,6 @@ 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; /** @@ -897,6 +897,50 @@ class JedisZSetCommands implements RedisZSetCommands { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRevRangeByLex(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range, org.springframework.data.redis.connection.RedisZSetCommands.Limit) + */ + @Override + public Set zRevRangeByLex(byte[] key, Range range, Limit limit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range for ZREVRANGEBYLEX must not be null!"); + Assert.notNull(limit, "Limit must not be null! Use Limit.unlimited() instead."); + + byte[] min = JedisConverters.boundaryToBytesForZRangeByLex(range.getMin(), JedisConverters.MINUS_BYTES); + byte[] max = JedisConverters.boundaryToBytesForZRangeByLex(range.getMax(), JedisConverters.PLUS_BYTES); + + try { + if (isPipelined()) { + if (!limit.isUnlimited()) { + pipeline(connection.newJedisResult( + connection.getRequiredPipeline().zrevrangeByLex(key, max, min, limit.getOffset(), limit.getCount()))); + } else { + pipeline(connection.newJedisResult(connection.getRequiredPipeline().zrevrangeByLex(key, max, min))); + } + return null; + } + + if (isQueueing()) { + if (!limit.isUnlimited()) { + transaction(connection.newJedisResult( + connection.getRequiredTransaction().zrevrangeByLex(key, max, min, limit.getOffset(), limit.getCount()))); + } else { + transaction(connection.newJedisResult(connection.getRequiredTransaction().zrevrangeByLex(key, max, min))); + } + return null; + } + + if (!limit.isUnlimited()) { + return new LinkedHashSet<>(connection.getJedis().zrevrangeByLex(key, max, min, limit.getOffset(), limit.getCount())); + } + return new LinkedHashSet<>(connection.getJedis().zrevrangeByLex(key, max, min)); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + private boolean isPipelined() { return connection.isPipelined(); } 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 69f027c6e..58baec869 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 @@ -37,6 +37,7 @@ import org.springframework.util.Assert; /** * @author Christoph Strobl * @author Mark Paluch + * @author Andrey Shlykov * @since 2.0 */ class LettuceZSetCommands implements RedisZSetCommands { @@ -877,6 +878,55 @@ class LettuceZSetCommands implements RedisZSetCommands { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRevRangeByLex(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range, org.springframework.data.redis.connection.RedisZSetCommands.Limit) + */ + @Override + public Set zRevRangeByLex(byte[] key, Range range, Limit limit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range for ZREVRANGEBYLEX must not be null!"); + Assert.notNull(limit, "Limit must not be null!"); + + try { + if (isPipelined()) { + if (limit.isUnlimited()) { + pipeline( + connection.newLettuceResult(getAsyncConnection().zrevrangebylex(key, LettuceConverters.toRange(range, true)), + LettuceConverters.bytesListToBytesSet())); + } else { + pipeline( + connection.newLettuceResult(getAsyncConnection().zrevrangebylex(key, LettuceConverters.toRange(range, true), + LettuceConverters.toLimit(limit)), LettuceConverters.bytesListToBytesSet())); + } + return null; + } + if (isQueueing()) { + if (limit.isUnlimited()) { + transaction( + connection.newLettuceResult(getAsyncConnection().zrevrangebylex(key, LettuceConverters.toRange(range, true)), + LettuceConverters.bytesListToBytesSet())); + } else { + transaction( + connection.newLettuceResult(getAsyncConnection().zrevrangebylex(key, LettuceConverters.toRange(range, true), + LettuceConverters.toLimit(limit)), LettuceConverters.bytesListToBytesSet())); + } + return null; + } + + if (limit.isUnlimited()) { + return LettuceConverters.bytesListToBytesSet() + .convert(getConnection().zrevrangebylex(key, LettuceConverters.toRange(range, true))); + } + return LettuceConverters.bytesListToBytesSet().convert( + getConnection().zrevrangebylex(key, LettuceConverters.toRange(range, true), LettuceConverters.toLimit(limit))); + + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + private boolean isPipelined() { return connection.isPipelined(); } 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 49aa2fbfd..ac505f919 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -2413,23 +2413,23 @@ public abstract class AbstractConnectionIntegrationTests { Set values = (Set) results.get(7); - assertThat(values).contains("a", "b", "c"); + assertThat(values).containsExactly("a", "b", "c"); assertThat(values).doesNotContain("d", "e", "f", "g"); values = (Set) results.get(8); - assertThat(values).contains("a", "b"); + assertThat(values).containsExactly("a", "b"); assertThat(values).doesNotContain("c"); values = (Set) results.get(9); - assertThat(values).contains("b", "c", "d", "e", "f"); + assertThat(values).containsExactly("b", "c", "d", "e", "f"); assertThat(values).doesNotContain("a", "g"); values = (Set) results.get(10); - assertThat(values).contains("e", "f", "g"); + assertThat(values).containsExactly("e", "f", "g"); assertThat(values).doesNotContain("a", "b", "c", "d"); values = (Set) results.get(11); - assertThat(values).contains("a", "b", "c"); + assertThat(values).containsExactly("a", "b", "c"); assertThat(values).doesNotContain("d", "e", "f", "g"); values = (Set) results.get(12); @@ -2441,6 +2441,61 @@ public abstract class AbstractConnectionIntegrationTests { assertThat(values).doesNotContain("a", "c", "d", "e", "f", "g"); } + @SuppressWarnings("unchecked") + @Test // DATAREDIS-729 + @IfProfileValue(name = "redisVersion", value = "2.9.0+") + @WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE }) + public void zRevRangeByLexTest() { + + actual.add(connection.zAdd("myzset", 0, "a")); + actual.add(connection.zAdd("myzset", 0, "b")); + actual.add(connection.zAdd("myzset", 0, "c")); + actual.add(connection.zAdd("myzset", 0, "d")); + actual.add(connection.zAdd("myzset", 0, "e")); + actual.add(connection.zAdd("myzset", 0, "f")); + actual.add(connection.zAdd("myzset", 0, "g")); + + actual.add(connection.zRevRangeByLex("myzset", Range.range().lte("c"))); + actual.add(connection.zRevRangeByLex("myzset", Range.range().lt("c"))); + actual.add(connection.zRevRangeByLex("myzset", Range.range().gte("aaa").lt("g"))); + actual.add(connection.zRevRangeByLex("myzset", Range.range().gte("e"))); + + actual.add(connection.zRevRangeByLex("myzset", Range.range().lte("c"), Limit.unlimited())); + actual.add(connection.zRevRangeByLex("myzset", Range.range().lte("d"), Limit.limit().count(2))); + actual.add(connection.zRevRangeByLex("myzset", Range.range().lte("d"), Limit.limit().count(2).offset(1))); + + List results = getResults(); + + Set values = (Set) results.get(7); + + assertThat(values).containsExactly("c", "b", "a"); + assertThat(values).doesNotContain("d", "e", "f", "g"); + + values = (Set) results.get(8); + assertThat(values).containsExactly("b", "a"); + assertThat(values).doesNotContain("c"); + + values = (Set) results.get(9); + assertThat(values).containsExactly("f", "e", "d", "c", "b"); + assertThat(values).doesNotContain("a", "g"); + + values = (Set) results.get(10); + assertThat(values).containsExactly("g", "f", "e"); + assertThat(values).doesNotContain("a", "b", "c", "d"); + + values = (Set) results.get(11); + assertThat(values).containsExactly("c", "b", "a"); + assertThat(values).doesNotContain("d", "e", "f", "g"); + + values = (Set) results.get(12); + assertThat(values).contains("d", "c"); + assertThat(values).doesNotContain("a", "b", "e", "f", "g"); + + values = (Set) results.get(13); + assertThat(values).contains("c", "b"); + assertThat(values).doesNotContain("a", "d", "e", "f", "g"); + } + @Test(expected = IllegalArgumentException.class) // DATAREDIS-316, DATAREDIS-692 @WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE }) public void setWithExpirationAndNullOpionShouldThrowException() {