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 f9a86b09c..a1095e88e 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -21,6 +21,7 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Queue; import java.util.Set; @@ -2236,6 +2237,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection { return this.delegate.sScan(key, options); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisHashCommands#hscan(byte[], org.springframework.data.redis.core.ScanOptions) + */ + @Override + public Cursor> hScan(byte[] key, ScanOptions options) { + return this.delegate.hScan(key, options); + } + /** * Specifies if pipelined and tx results should be deserialized to Strings. If false, results of * {@link #closePipeline()} and {@link #exec()} will be of the type returned by the underlying connection @@ -2328,4 +2338,36 @@ public class DefaultStringRedisConnection implements StringRedisConnection { } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#hScan(java.lang.String, org.springframework.data.redis.core.ScanOptions) + */ + @Override + public Cursor> hScan(String key, ScanOptions options) { + + return new ConvertingCursor, Map.Entry>(this.delegate.hScan( + this.serialize(key), options), new Converter, Map.Entry>() { + + @Override + public Entry convert(final Entry source) { + return new Map.Entry() { + + @Override + public String getKey() { + return DefaultStringRedisConnection.this.serializer.deserialize(source.getKey()); + } + + @Override + public String getValue() { + return DefaultStringRedisConnection.this.serializer.deserialize(source.getValue()); + } + + @Override + public String setValue(String value) { + throw new UnsupportedOperationException("Cannot set value for entry in cursor"); + } + }; + } + }); + } } diff --git a/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java index 3a0bbfc7d..b419615e3 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java @@ -19,6 +19,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.ScanOptions; + /** * Hash-specific commands supported by Redis. * @@ -155,4 +158,14 @@ public interface RedisHashCommands { * @return */ Map hGetAll(byte[] key); + + /** + * Use a {@link Cursor} to iterate over entries in hash at {@code key}. + * + * @param key + * @param options + * @return + * @since 1.4 + */ + Cursor> hScan(byte[] key, ScanOptions options); } 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 b357a1a84..e58c58877 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -327,4 +327,12 @@ public interface StringRedisConnection extends RedisConnection { * @since 1.4 */ Cursor sScan(String key, ScanOptions options); + + /** + * @param key + * @param options + * @return + * @since 1.4 + */ + Cursor> hScan(String key, ScanOptions options); } diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java index de00f447b..a1340e8e7 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Queue; import java.util.Set; @@ -65,6 +66,7 @@ import redis.clients.jedis.Protocol.Command; import redis.clients.jedis.Queable; import redis.clients.jedis.Response; import redis.clients.jedis.ScanParams; +import redis.clients.jedis.ScanResult; import redis.clients.jedis.SortingParams; import redis.clients.jedis.Transaction; import redis.clients.jedis.ZParams; @@ -2925,6 +2927,7 @@ public class JedisConnection implements RedisConnection { * @param options * @return */ + @SuppressWarnings("resource") public Cursor scan(long cursorId, ScanOptions options) { return new ScanCursor(cursorId, options) { @@ -2981,6 +2984,30 @@ public class JedisConnection implements RedisConnection { }.open(); } + @Override + public Cursor> hScan(byte[] key, ScanOptions options) { + return hscan(key, 0, options); + } + + public Cursor> hscan(byte[] key, long cursorId, ScanOptions options) { + + return new KeyBoundCursor>(key, cursorId, options) { + + @Override + protected ScanIteration> doScan(byte[] key, long cursorId, ScanOptions options) { + + if (isQueueing() || isPipelined()) { + throw new UnsupportedOperationException("'HSCAN' cannot be called in pipeline / transaction mode."); + } + + ScanParams params = prepareScanParams(options); + + ScanResult> result = jedis.hscan(key, JedisConverters.toBytes(cursorId), params); + return new ScanIteration>(Long.valueOf(result.getStringCursor()), result.getResult()); + } + }.open(); + } + private ScanParams prepareScanParams(ScanOptions options) { ScanParams sp = new ScanParams(); if (!options.equals(ScanOptions.NONE)) { diff --git a/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java b/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java index ae0c7a4a1..d6e85c4b8 100644 --- a/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Set; @@ -1256,4 +1257,13 @@ public class JredisConnection implements RedisConnection { public Cursor sScan(byte[] key, ScanOptions options) { throw new UnsupportedOperationException("'SSCAN' command is not uspported for jredis"); } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisHashCommands#hscan(byte[], org.springframework.data.redis.core.ScanOptions) + */ + @Override + public Cursor> hScan(byte[] key, ScanOptions options) { + throw new UnsupportedOperationException("'HSCAN' command is not uspported for jredis"); + } } 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 7ebb394ab..9cab349de 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 @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Queue; import java.util.Set; @@ -59,7 +60,6 @@ import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; import com.lambdaworks.redis.RedisAsyncConnection; import com.lambdaworks.redis.RedisClient; @@ -3063,7 +3063,7 @@ public class LettuceConnection implements RedisConnection { throw new UnsupportedOperationException("'SCAN' cannot be called in pipeline / transaction mode."); } - String params = " ," + cursorId + prepareScanParams(options); + String params = " ," + cursorId + options.toOptionString(); String script = "return redis.call('SCAN'" + params + ")"; List result = eval(script.getBytes(), ReturnType.MULTI, 0); @@ -3104,7 +3104,7 @@ public class LettuceConnection implements RedisConnection { } String params = " ,'" + LettuceConverters.bytesToString().convert(key) + "', " + cursorId - + prepareScanParams(options); + + options.toOptionString(); String script = "return redis.call('SSCAN'" + params + ")"; List result = eval(script.getBytes(), ReturnType.MULTI, 0); @@ -3115,18 +3115,44 @@ public class LettuceConnection implements RedisConnection { }.open(); } - private String prepareScanParams(ScanOptions options) { + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisHashCommands#hScan(byte[], org.springframework.data.redis.core.ScanOptions) + */ + @Override + public Cursor> hScan(byte[] key, ScanOptions options) { + return hscan(key, 0, options); + } - String params = ""; - if (!options.equals(ScanOptions.NONE)) { - if (options.getCount() != null) { - params += (", 'count', " + options.getCount()); + /** + * @param key + * @param cursorId + * @param options + * @return + * @since 1.4 + */ + public Cursor> hscan(byte[] key, long cursorId, ScanOptions options) { + + return new KeyBoundCursor>(key, cursorId, options) { + + @Override + protected ScanIteration> doScan(byte[] key, long cursorId, ScanOptions options) { + + if (isQueueing() || isPipelined()) { + throw new UnsupportedOperationException("'HSCAN' cannot be called in pipeline / transaction mode."); + } + + String params = " ,'" + LettuceConverters.bytesToString().convert(key) + "', " + cursorId + + options.toOptionString(); + String script = "return redis.call('HSCAN'" + params + ")"; + + List result = eval(script.getBytes(), ReturnType.MULTI, 0); + String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0)); + + @SuppressWarnings("unchecked") + Map values = LettuceConverters.toMap((List) result.get(1)); + return new ScanIteration>(Long.valueOf(nextCursorId), values.entrySet()); } - if (StringUtils.hasText(options.getPattern())) { - params += (", 'match' , '" + options.getPattern() + "'"); - } - } - return params; + }.open(); } /** 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 720ea95e2..cd2103fda 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 @@ -19,8 +19,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.springframework.core.convert.converter.Converter; @@ -36,6 +39,7 @@ import org.springframework.data.redis.connection.convert.LongToBooleanConverter; import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter; import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.lambdaworks.redis.KeyValue; @@ -62,6 +66,7 @@ abstract public class LettuceConverters extends Converters { private static final Converter, Tuple> SCORED_VALUE_TO_TUPLE; private static final Converter EXCEPTION_CONVERTER = new LettuceExceptionConverter(); private static final Converter LONG_TO_BOOLEAN = new LongToBooleanConverter(); + private static final Converter, Map> BYTES_LIST_TO_MAP; private static final Converter> STRING_TO_LIST_OF_CLIENT_INFO = new StringToRedisClientInfoConverter(); @@ -105,6 +110,26 @@ abstract public class LettuceConverters extends Converters { return list; } }; + BYTES_LIST_TO_MAP = new Converter, Map>() { + + @Override + public Map convert(final List source) { + + if (CollectionUtils.isEmpty(source)) { + Collections.emptyMap(); + } + + Map target = new LinkedHashMap(); + + Iterator kv = source.iterator(); + while (kv.hasNext()) { + target.put(kv.next(), kv.hasNext() ? kv.next() : null); + } + + return target; + } + + }; SCORED_VALUES_TO_TUPLE_SET = new Converter>, Set>() { public Set convert(List> source) { if (source == null) { @@ -238,6 +263,10 @@ abstract public class LettuceConverters extends Converters { return (value ? 1 : 0); } + public static Map toMap(List source) { + return BYTES_LIST_TO_MAP.convert(source); + } + public static SortArgs toSortArgs(SortParameters params) { SortArgs args = new SortArgs(); if (params == null) { diff --git a/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java b/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java index cf55276e5..89423f4b1 100644 --- a/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Queue; import java.util.Set; @@ -2351,6 +2352,15 @@ public class SrpConnection implements RedisConnection { throw new UnsupportedOperationException("'SSCAN' command is not supported for Srp."); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisHashCommands#hscan(byte[], org.springframework.data.redis.core.ScanOptions) + */ + @Override + public Cursor> hScan(byte[] key, ScanOptions options) { + throw new UnsupportedOperationException("'HSCAN' command is not supported for Srp."); + } + private List closeTransaction() { List results = Collections.emptyList(); if (txTracker != null) { diff --git a/src/main/java/org/springframework/data/redis/core/BoundHashOperations.java b/src/main/java/org/springframework/data/redis/core/BoundHashOperations.java index 54e0a3648..219208b10 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundHashOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundHashOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.data.redis.core; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -24,6 +25,7 @@ import java.util.Set; * Hash operations bound to a certain key. * * @author Costin Leau + * @author Christoph Strobl */ public interface BoundHashOperations extends BoundKeyOperations { @@ -54,4 +56,11 @@ public interface BoundHashOperations extends BoundKeyOperations { void delete(Object... keys); Map entries(); + + /** + * @param key + * @since 1.4 + * @return + */ + Iterator> scan(H key, ScanOptions options); } diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundHashOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundHashOperations.java index 382b46569..00ca107a0 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundHashOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundHashOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,10 @@ package org.springframework.data.redis.core; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.springframework.data.redis.connection.DataType; @@ -26,6 +28,7 @@ import org.springframework.data.redis.connection.DataType; * Default implementation for {@link HashOperations}. * * @author Costin Leau + * @author Christoph Strobl */ class DefaultBoundHashOperations extends DefaultBoundKeyOperations implements BoundHashOperations { @@ -102,4 +105,13 @@ class DefaultBoundHashOperations extends DefaultBoundKeyOperations public DataType getType() { return DataType.HASH; } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundHashOperations#hscan(java.lang.Object) + */ + @Override + public Iterator> scan(H key, ScanOptions options) { + return ops.scan(key, options); + } } diff --git a/src/main/java/org/springframework/data/redis/core/DefaultHashOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultHashOperations.java index bb54463f4..3984296e9 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultHashOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultHashOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,17 +17,22 @@ package org.springframework.data.redis.core; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import org.springframework.core.convert.converter.Converter; +import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; /** * Default implementation of {@link HashOperations}. * * @author Costin Leau + * @author Christoph Strobl */ class DefaultHashOperations extends AbstractOperations implements HashOperations { @@ -222,4 +227,49 @@ class DefaultHashOperations extends AbstractOperations imp return deserializeHashMap(entries); } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.HashOperations#hscan(java.lang.Object, org.springframework.data.redis.core.ScanOptions) + */ + @Override + public Iterator> scan(K key, final ScanOptions options) { + + final byte[] rawKey = rawKey(key); + return execute(new RedisCallback>>() { + + @Override + public Cursor> doInRedis(RedisConnection connection) throws DataAccessException { + + return new ConvertingCursor, Map.Entry>(connection.hScan(rawKey, options), + new Converter, Map.Entry>() { + + @Override + public Entry convert(final Entry source) { + + return new Map.Entry() { + + @Override + public HK getKey() { + return deserializeHashKey(source.getKey()); + } + + @Override + public HV getValue() { + return deserializeHashValue(source.getValue()); + } + + @Override + public HV setValue(HV value) { + throw new UnsupportedOperationException("Values cannot be set when scanning through entries."); + } + }; + + } + }); + } + + }, true); + + } } diff --git a/src/main/java/org/springframework/data/redis/core/HashOperations.java b/src/main/java/org/springframework/data/redis/core/HashOperations.java index ad3bc9506..20493af95 100644 --- a/src/main/java/org/springframework/data/redis/core/HashOperations.java +++ b/src/main/java/org/springframework/data/redis/core/HashOperations.java @@ -16,6 +16,7 @@ package org.springframework.data.redis.core; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -54,4 +55,12 @@ public interface HashOperations { Map entries(H key); RedisOperations getOperations(); + + /** + * @param key + * @since 1.4 + * @param options + * @return + */ + Iterator> scan(H key, ScanOptions options); } diff --git a/src/main/java/org/springframework/data/redis/core/ScanIteration.java b/src/main/java/org/springframework/data/redis/core/ScanIteration.java index 77b6edd5e..edbc7e6ca 100644 --- a/src/main/java/org/springframework/data/redis/core/ScanIteration.java +++ b/src/main/java/org/springframework/data/redis/core/ScanIteration.java @@ -16,9 +16,9 @@ package org.springframework.data.redis.core; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; -import java.util.List; /** * {@link ScanIteration} holds the values contained in Redis {@literal Multibulk reply} on exectuting {@literal SCAN} @@ -30,13 +30,13 @@ import java.util.List; public class ScanIteration implements Iterable { private final long cursorId; - private final List items; + private final Collection items; /** * @param cursorId * @param items */ - public ScanIteration(long cursorId, List items) { + public ScanIteration(long cursorId, Collection items) { this.cursorId = cursorId; this.items = (items != null ? new ArrayList(items) : Collections. emptyList()); @@ -56,7 +56,7 @@ public class ScanIteration implements Iterable { * * @return */ - public List getItems() { + public Collection getItems() { return items; } diff --git a/src/main/java/org/springframework/data/redis/core/ScanOptions.java b/src/main/java/org/springframework/data/redis/core/ScanOptions.java index 357f429cf..83bc1a8ef 100644 --- a/src/main/java/org/springframework/data/redis/core/ScanOptions.java +++ b/src/main/java/org/springframework/data/redis/core/ScanOptions.java @@ -15,6 +15,8 @@ */ package org.springframework.data.redis.core; +import org.springframework.util.StringUtils; + /** * Options to be used for with {@literal SCAN} command. * @@ -29,15 +31,14 @@ public class ScanOptions { private Long count; private String pattern; - private ScanOptions() { - } + private ScanOptions() {} /** * Static factory method that returns a new {@link ScanOptionsBuilder}. * * @return */ - public static ScanOptionsBuilder scanOptions(){ + public static ScanOptionsBuilder scanOptions() { return new ScanOptionsBuilder(); } @@ -49,6 +50,24 @@ public class ScanOptions { return pattern; } + public String toOptionString() { + + if (this.equals(ScanOptions.NONE)) { + return ""; + } + + String params = ""; + + if (this.count != null) { + params += (", 'count', " + count); + } + if (StringUtils.hasText(this.pattern)) { + params += (", 'match' , '" + this.pattern + "'"); + } + + return params; + } + /** * @author Christoph Strobl * @since 1.4 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 b139d19b6..a74dfd3c4 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -1979,6 +1979,44 @@ public abstract class AbstractConnectionIntegrationTests { assertThat(i, is(6)); } + /** + * @see DATAREDIS-305 + */ + @Test + @IfProfileValue(name = "redisVersion", value = "2.8+") + public void hScanShouldReadEntireValueRange() { + + if (!ConnectionUtils.isJedis(connectionFactory) && !ConnectionUtils.isLettuce(connectionFactory)) { + throw new AssumptionViolatedException("HSCAN is only available for jedis and lettuce"); + } + + if (connection.isPipelined() || connection.isQueueing()) { + throw new AssumptionViolatedException("HSCAN is only available in non pipeline | queue mode."); + } + + connection.hSet("hscankey", "bar", "foobar"); + + connection.hSet("hscankey", "foo-1", "v-1"); + connection.hSet("hscankey", "foo-2", "v-2"); + connection.hSet("hscankey", "foo-3", "v-3"); + + Cursor> cursor = connection + .hScan("hscankey", scanOptions().count(2).match("fo*").build()); + + int i = 0; + while (cursor.hasNext()) { + + String key = cursor.next().getKey(); + + assertThat(key, not(containsString("bar"))); + assertThat(key, containsString("foo")); + + i++; + } + + assertThat(i, is(3)); + } + protected void verifyResults(List expected) { assertEquals(expected, getResults()); } diff --git a/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsTests.java b/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsTests.java index e1d4b8fff..0f92b36ce 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,12 @@ */ package org.springframework.data.redis.core; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.springframework.data.redis.matcher.RedisTestMatchers.isEqual; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; import java.util.Arrays; import java.util.Collection; -import java.util.LinkedHashMap; +import java.util.Iterator; import java.util.Map; import org.junit.After; @@ -35,12 +34,14 @@ import org.springframework.data.redis.RawObjectFactory; import org.springframework.data.redis.SettingsUtils; import org.springframework.data.redis.StringObjectFactory; import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.srp.SrpConnectionFactory; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.test.annotation.IfProfileValue; /** * Integration test of {@link DefaultHashOperations} * * @author Jennifer Hickey + * @author Christoph Strobl * @param Key type * @param Hash key type * @param Hash value type @@ -70,17 +71,17 @@ public class DefaultHashOperationsTests { ObjectFactory stringFactory = new StringObjectFactory(); ObjectFactory rawFactory = new RawObjectFactory(); - SrpConnectionFactory srConnFactory = new SrpConnectionFactory(); - srConnFactory.setPort(SettingsUtils.getPort()); - srConnFactory.setHostName(SettingsUtils.getHost()); - srConnFactory.afterPropertiesSet(); + JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); + jedisConnectionFactory.setPort(SettingsUtils.getPort()); + jedisConnectionFactory.setHostName(SettingsUtils.getHost()); + jedisConnectionFactory.afterPropertiesSet(); RedisTemplate stringTemplate = new StringRedisTemplate(); - stringTemplate.setConnectionFactory(srConnFactory); + stringTemplate.setConnectionFactory(jedisConnectionFactory); stringTemplate.afterPropertiesSet(); RedisTemplate rawTemplate = new RedisTemplate(); - rawTemplate.setConnectionFactory(srConnFactory); + rawTemplate.setConnectionFactory(jedisConnectionFactory); rawTemplate.setEnableDefaultSerializer(false); rawTemplate.afterPropertiesSet(); @@ -112,10 +113,11 @@ public class DefaultHashOperationsTests { HV val2 = hashValueFactory.instance(); hashOps.put(key, key1, val1); hashOps.put(key, key2, val2); - Map expected = new LinkedHashMap(); - expected.put(key1, val1); - expected.put(key2, val2); - assertThat(hashOps.entries(key), isEqual(expected)); + + for (Map.Entry entry : hashOps.entries(key).entrySet()) { + assertThat(entry.getKey(), anyOf(equalTo(key1), equalTo(key2))); + assertThat(entry.getValue(), anyOf(equalTo(val1), equalTo(val2))); + } } @Test @@ -130,4 +132,33 @@ public class DefaultHashOperationsTests { hashOps.delete(key, key1, key2); assertTrue(hashOps.keys(key).isEmpty()); } + + /** + * @see DATAREDIS-305 + */ + @Test + @IfProfileValue(name = "redisVersion", value = "2.8+") + public void testHScanReadsValuesFully() { + + K key = keyFactory.instance(); + HK key1 = hashKeyFactory.instance(); + HV val1 = hashValueFactory.instance(); + HK key2 = hashKeyFactory.instance(); + HV val2 = hashValueFactory.instance(); + hashOps.put(key, key1, val1); + hashOps.put(key, key2, val2); + + Iterator> it = hashOps.scan(key, ScanOptions.scanOptions().count(1).build()); + + long count = 0; + while (it.hasNext()) { + Map.Entry entry = it.next(); + assertThat(entry.getKey(), anyOf(equalTo(key1), equalTo(key2))); + assertThat(entry.getValue(), anyOf(equalTo(val1), equalTo(val2))); + count++; + } + + assertThat(count, is(hashOps.size(key))); + } + }