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 161fc5d7b..17f477223 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java @@ -19,6 +19,7 @@ package org.springframework.data.redis.core; import java.util.Collection; import java.util.Set; +import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; /** @@ -26,6 +27,7 @@ import org.springframework.data.redis.core.ZSetOperations.TypedTuple; * * @author Costin Leau * @author Christoph Strobl + * @author Mark Paluch */ public interface BoundZSetOperations extends BoundKeyOperations { @@ -51,6 +53,10 @@ public interface BoundZSetOperations extends BoundKeyOperations { Set> reverseRangeByScoreWithScores(double min, double max); + Set rangeByLex(RedisZSetCommands.Range range); + + Set rangeByLex(RedisZSetCommands.Range range, RedisZSetCommands.Limit limit); + void removeRange(long start, long end); void removeRangeByScore(double min, double max); 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 6d60d6710..e5797c870 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Set; import org.springframework.data.redis.connection.DataType; +import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; /** @@ -27,6 +28,7 @@ import org.springframework.data.redis.core.ZSetOperations.TypedTuple; * * @author Costin Leau * @author Christoph Strobl + * @author Mark Paluch */ class DefaultBoundZSetOperations extends DefaultBoundKeyOperations implements BoundZSetOperations { @@ -36,7 +38,7 @@ class DefaultBoundZSetOperations extends DefaultBoundKeyOperations impl * Constructs a new DefaultBoundZSetOperations instance. * * @param key - * @param oeprations + * @param operations */ public DefaultBoundZSetOperations(K key, RedisOperations operations) { super(key, operations); @@ -95,6 +97,16 @@ class DefaultBoundZSetOperations extends DefaultBoundKeyOperations impl return ops.reverseRangeWithScores(getKey(), start, end); } + @Override + public Set rangeByLex(RedisZSetCommands.Range range) { + return ops.rangeByLex(getKey(), range); + } + + @Override + public Set rangeByLex(RedisZSetCommands.Range range, RedisZSetCommands.Limit limit) { + return ops.rangeByLex(getKey(), range, limit); + } + public Long rank(Object o) { return ops.rank(getKey(), o); } 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 e8dc1f973..bfe2cd1cf 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java @@ -22,6 +22,7 @@ import java.util.Set; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; /** @@ -31,6 +32,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; * @author Christoph Strobl * @author Thomas Darimont * @author David Liu + * @author Mark Paluch */ class DefaultZSetOperations extends AbstractOperations implements ZSetOperations { @@ -141,6 +143,36 @@ class DefaultZSetOperations extends AbstractOperations implements ZS return deserializeTupleValues(rawValues); } + @Override + public Set rangeByLex(K key, final RedisZSetCommands.Range range) { + + final byte[] rawKey = rawKey(key); + + Set rawValues = execute(new RedisCallback>() { + + public Set doInRedis(RedisConnection connection) { + return connection.zRangeByLex(rawKey, range); + } + }, true); + + return deserializeValues(rawValues); + } + + @Override + public Set rangeByLex(K key, final RedisZSetCommands.Range range, final RedisZSetCommands.Limit limit) { + + final byte[] rawKey = rawKey(key); + + Set rawValues = execute(new RedisCallback>() { + + public Set doInRedis(RedisConnection connection) { + return connection.zRangeByLex(rawKey, range, limit); + } + }, true); + + return deserializeValues(rawValues); + } + public Set rangeByScore(K key, final double min, final double max) { final byte[] rawKey = rawKey(key); 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 d5df5c344..ad6fec441 100644 --- a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java @@ -19,11 +19,14 @@ package org.springframework.data.redis.core; import java.util.Collection; import java.util.Set; +import org.springframework.data.redis.connection.RedisZSetCommands; + /** * Redis ZSet/sorted set specific operations. * * @author Costin Leau * @author Christoph Strobl + * @author Mark Paluch */ public interface ZSetOperations { @@ -52,6 +55,10 @@ public interface ZSetOperations { Set> reverseRangeWithScores(K key, long start, long end); + Set rangeByLex(K key, RedisZSetCommands.Range range); + + Set rangeByLex(K key, RedisZSetCommands.Range range, RedisZSetCommands.Limit limit); + Set rangeByScore(K key, double min, double max); Set rangeByScore(K key, double min, double max, long offset, long count); 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 deb8d2473..3e7bdc019 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 @@ -22,6 +22,7 @@ import java.util.Set; import org.springframework.core.convert.converter.Converter; import org.springframework.data.redis.connection.DataType; +import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.core.BoundZSetOperations; import org.springframework.data.redis.core.ConvertingCursor; import org.springframework.data.redis.core.Cursor; @@ -35,6 +36,7 @@ import org.springframework.data.redis.core.ZSetOperations.TypedTuple; * * @author Costin Leau * @author Christoph Strobl + * @author Mark Paluch */ public class DefaultRedisZSet extends AbstractRedisCollection implements RedisZSet { @@ -114,6 +116,16 @@ public class DefaultRedisZSet extends AbstractRedisCollection implements R return boundZSetOps.reverseRange(start, end); } + @Override + public Set rangeByLex(RedisZSetCommands.Range range) { + return boundZSetOps.rangeByLex(range); + } + + @Override + public Set rangeByLex(RedisZSetCommands.Range range, RedisZSetCommands.Limit limit) { + return boundZSetOps.rangeByLex(range, limit); + } + public Set rangeByScore(double min, double max) { return boundZSetOps.rangeByScore(min, max); } 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 19e6bc63d..caacf3ba8 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 @@ -22,6 +22,7 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; +import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; /** @@ -31,6 +32,7 @@ import org.springframework.data.redis.core.ZSetOperations.TypedTuple; * Since using a {@link Comparator} does not apply, a ZSet implements the {@link SortedSet} methods where applicable. * * @author Costin Leau + * @author Mark Paluch */ public interface RedisZSet extends RedisCollection, Set { @@ -46,6 +48,10 @@ public interface RedisZSet extends RedisCollection, Set { Set reverseRange(long start, long end); + Set rangeByLex(RedisZSetCommands.Range range); + + Set rangeByLex(RedisZSetCommands.Range range, RedisZSetCommands.Limit limit); + Set rangeByScore(double min, double max); Set reverseRangeByScore(double min, double max); diff --git a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsTests.java b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsTests.java index cc138d7e9..c35dcc3d8 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsTests.java @@ -17,7 +17,9 @@ package org.springframework.data.redis.core; import static org.hamcrest.core.AnyOf.*; import static org.hamcrest.core.IsEqual.*; +import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.*; +import static org.junit.Assume.assumeThat; import static org.springframework.data.redis.matcher.RedisTestMatchers.*; import java.util.Collection; @@ -34,8 +36,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import org.springframework.data.redis.DoubleAsStringObjectFactory; +import org.springframework.data.redis.DoubleObjectFactory; +import org.springframework.data.redis.LongAsStringObjectFactory; +import org.springframework.data.redis.LongObjectFactory; import org.springframework.data.redis.ObjectFactory; import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; import org.springframework.data.redis.test.util.MinimumRedisVersionRule; import org.springframework.test.annotation.IfProfileValue; @@ -45,6 +52,7 @@ import org.springframework.test.annotation.IfProfileValue; * * @author Jennifer Hickey * @author Christoph Strobl + * @author Mark Paluch * @param Key type * @param Value type */ @@ -165,6 +173,104 @@ public class DefaultZSetOperationsTests { assertThat(tuple, isEqual(new DefaultTypedTuple(value2, 3.7))); } + /** + * @see DATAREDIS-407 + */ + @Test + public void testRangeByLexUnbounded() { + + assumeThat(valueFactory, anyOf(instanceOf(DoubleObjectFactory.class), instanceOf(DoubleAsStringObjectFactory.class), + instanceOf(LongAsStringObjectFactory.class), instanceOf(LongObjectFactory.class))); + + 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); + Set tuples = zSetOps.rangeByLex(key, RedisZSetCommands.Range.unbounded()); + + assertEquals(3, tuples.size()); + V tuple = tuples.iterator().next(); + assertThat(tuple, isEqual(value1)); + } + + /** + * @see DATAREDIS-407 + */ + @Test + public void testRangeByLexBounded() { + + assumeThat(valueFactory, anyOf(instanceOf(DoubleObjectFactory.class), instanceOf(DoubleAsStringObjectFactory.class), + instanceOf(LongAsStringObjectFactory.class), instanceOf(LongObjectFactory.class))); + + 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); + Set tuples = zSetOps.rangeByLex(key, RedisZSetCommands.Range.range().gt(value1).lt(value3)); + + assertEquals(1, tuples.size()); + V tuple = tuples.iterator().next(); + assertThat(tuple, isEqual(value2)); + } + + /** + * @see DATAREDIS-407 + */ + @Test + public void testRangeByLexUnboundedWithLimit() { + + assumeThat(valueFactory, anyOf(instanceOf(DoubleObjectFactory.class), instanceOf(DoubleAsStringObjectFactory.class), + instanceOf(LongAsStringObjectFactory.class), instanceOf(LongObjectFactory.class))); + + 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); + Set tuples = zSetOps.rangeByLex(key, RedisZSetCommands.Range.unbounded(), + RedisZSetCommands.Limit.limit().count(1).offset(1)); + + assertEquals(1, tuples.size()); + V tuple = tuples.iterator().next(); + assertThat(tuple, isEqual(value2)); + } + + /** + * @see DATAREDIS-407 + */ + @Test + public void testRangeByLexBoundedWithLimit() { + + assumeThat(valueFactory, anyOf(instanceOf(DoubleObjectFactory.class), instanceOf(DoubleAsStringObjectFactory.class), + instanceOf(LongAsStringObjectFactory.class), instanceOf(LongObjectFactory.class))); + + 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); + Set tuples = zSetOps.rangeByLex(key, RedisZSetCommands.Range.range().gte(value1), + RedisZSetCommands.Limit.limit().count(1).offset(1)); + + assertEquals(1, tuples.size()); + V tuple = tuples.iterator().next(); + assertThat(tuple, isEqual(value2)); + } + @Test public void testAddMultiple() { K key = keyFactory.instance(); diff --git a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTest.java b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTest.java index f737a71f6..8ead1934d 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTest.java +++ b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisZSetTest.java @@ -28,9 +28,14 @@ import java.util.Set; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.springframework.data.redis.DoubleAsStringObjectFactory; +import org.springframework.data.redis.DoubleObjectFactory; +import org.springframework.data.redis.LongAsStringObjectFactory; +import org.springframework.data.redis.LongObjectFactory; import org.springframework.data.redis.ObjectFactory; import org.springframework.data.redis.connection.ConnectionUtils; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.core.BoundZSetOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; @@ -46,6 +51,7 @@ import org.springframework.test.annotation.IfProfileValue; * @author Costin Leau * @author Jennifer Hickey * @author Thomas Darimont + * @author Mark Paluch */ public abstract class AbstractRedisZSetTest extends AbstractRedisCollectionTests { @@ -320,6 +326,100 @@ public abstract class AbstractRedisZSetTest extends AbstractRedisCollectionTe assertThat(tuple2.getScore(), isEqual(Double.valueOf(1))); } + /** + * @see DATAREDIS-407 + */ + @Test + public void testRangeByLexUnbounded() { + + assumeThat(factory, anyOf(instanceOf(DoubleObjectFactory.class), instanceOf(DoubleAsStringObjectFactory.class), + instanceOf(LongAsStringObjectFactory.class), instanceOf(LongObjectFactory.class))); + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + Set tuples = zSet.rangeByLex(RedisZSetCommands.Range.unbounded()); + + assertEquals(3, tuples.size()); + T tuple = tuples.iterator().next(); + assertThat(tuple, isEqual(t1)); + } + + /** + * @see DATAREDIS-407 + */ + @Test + public void testRangeByLexBounded() { + + assumeThat(factory, anyOf(instanceOf(DoubleObjectFactory.class), instanceOf(DoubleAsStringObjectFactory.class), + instanceOf(LongAsStringObjectFactory.class), instanceOf(LongObjectFactory.class))); + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + Set tuples = zSet.rangeByLex(RedisZSetCommands.Range.range().gt(t1).lt(t3)); + + assertEquals(1, tuples.size()); + T tuple = tuples.iterator().next(); + assertThat(tuple, isEqual(t2)); + } + + /** + * @see DATAREDIS-407 + */ + @Test + public void testRangeByLexUnboundedWithLimit() { + + assumeThat(factory, anyOf(instanceOf(DoubleObjectFactory.class), instanceOf(DoubleAsStringObjectFactory.class), + instanceOf(LongAsStringObjectFactory.class), instanceOf(LongObjectFactory.class))); + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + Set tuples = zSet.rangeByLex(RedisZSetCommands.Range.unbounded(), + RedisZSetCommands.Limit.limit().count(1).offset(1)); + + assertEquals(1, tuples.size()); + T tuple = tuples.iterator().next(); + assertThat(tuple, isEqual(t2)); + } + + /** + * @see DATAREDIS-407 + */ + @Test + public void testRangeByLexBoundedWithLimit() { + + assumeThat(factory, anyOf(instanceOf(DoubleObjectFactory.class), instanceOf(DoubleAsStringObjectFactory.class), + instanceOf(LongAsStringObjectFactory.class), instanceOf(LongObjectFactory.class))); + + T t1 = getT(); + T t2 = getT(); + T t3 = getT(); + + zSet.add(t1, 1); + zSet.add(t2, 2); + zSet.add(t3, 3); + Set tuples = zSet.rangeByLex(RedisZSetCommands.Range.range().gte(t1), + RedisZSetCommands.Limit.limit().count(1).offset(1)); + + assertEquals(1, tuples.size()); + T tuple = tuples.iterator().next(); + assertThat(tuple, isEqual(t2)); + } + @Test public void testReverseRangeByScore() { assumeTrue(!ConnectionUtils.isJredis(template.getConnectionFactory())); diff --git a/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java b/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java index c0d844091..9a82b84b2 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java +++ b/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java @@ -18,6 +18,7 @@ package org.springframework.data.redis.support.collections; import java.util.Arrays; import java.util.Collection; +import org.springframework.data.redis.DoubleAsStringObjectFactory; import org.springframework.data.redis.ObjectFactory; import org.springframework.data.redis.Person; import org.springframework.data.redis.PersonObjectFactory; @@ -40,6 +41,7 @@ import org.springframework.oxm.xstream.XStreamMarshaller; /** * @author Costin Leau * @author Thomas Darimont + * @author Mark Paluch */ public abstract class CollectionTestParams { @@ -58,6 +60,7 @@ public abstract class CollectionTestParams { // create Jedis Factory ObjectFactory stringFactory = new StringObjectFactory(); + ObjectFactory doubleAsStringObjectFactory = new DoubleAsStringObjectFactory(); ObjectFactory personFactory = new PersonObjectFactory(); ObjectFactory rawFactory = new RawObjectFactory(); @@ -214,6 +217,7 @@ public abstract class CollectionTestParams { return Arrays.asList(new Object[][] { { stringFactory, stringTemplate }, + { doubleAsStringObjectFactory, stringTemplate }, { personFactory, personTemplate }, { stringFactory, xstreamStringTemplate }, { personFactory, xstreamPersonTemplate }, @@ -237,6 +241,7 @@ public abstract class CollectionTestParams { { rawFactory, rawTemplateSRP }, // lettuce { stringFactory, stringTemplateLtc }, { personFactory, personTemplateLtc }, + { doubleAsStringObjectFactory, stringTemplateLtc }, { personFactory, personTemplateLtc }, { stringFactory, xstreamStringTemplateLtc }, { personFactory, xstreamPersonTemplateLtc }, { personFactory, jsonPersonTemplateLtc }, { personFactory, jackson2JsonPersonTemplateLtc }, { rawFactory, rawTemplateLtc } });