From a3ac8d0899fea4578358a42ed98781992ad010e5 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 18 Mar 2014 07:30:04 +0100 Subject: [PATCH] DATAREDIS-285 - LettuceConnection.execute should fully read response. 'LettuceConnection' used 'ByteArrayOutput' as default for execute which did not fully read all values that might be included in the response. To overcome this issue 'TypeHints' have been added which map a given 'CommandType' to the most recent 'CommandOutput'. As not all commands could be mapped that way due to the result depending on input parameters in some cases, it might be required to manually provide the type hint when calling execute. Additional tests have been added to assert 'execute' works properly for 'jedis', 'jredis' and 'srp'. Original Pull Request: #49 --- .../connection/lettuce/LettuceConnection.java | 242 +++++++++++++++++- .../JedisConnectionIntegrationTests.java | 20 ++ .../JRedisConnectionIntegrationTests.java | 23 ++ .../LettuceConnectionIntegrationTests.java | 20 ++ .../srp/SrpConnectionIntegrationTests.java | 24 ++ 5 files changed, 324 insertions(+), 5 deletions(-) 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 7f6680cb3..98044a813 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 @@ -17,18 +17,22 @@ package org.springframework.data.redis.connection.lettuce; import static com.lambdaworks.redis.protocol.CommandType.*; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Queue; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.springframework.beans.BeanUtils; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -46,6 +50,7 @@ import org.springframework.data.redis.connection.Subscription; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.connection.convert.TransactionResultConverter; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import com.lambdaworks.redis.RedisAsyncConnection; @@ -54,9 +59,22 @@ import com.lambdaworks.redis.RedisException; import com.lambdaworks.redis.SortArgs; import com.lambdaworks.redis.ZStoreArgs; import com.lambdaworks.redis.codec.RedisCodec; +import com.lambdaworks.redis.output.BooleanOutput; import com.lambdaworks.redis.output.ByteArrayOutput; +import com.lambdaworks.redis.output.DateOutput; +import com.lambdaworks.redis.output.DoubleOutput; +import com.lambdaworks.redis.output.IntegerOutput; +import com.lambdaworks.redis.output.KeyListOutput; +import com.lambdaworks.redis.output.KeyValueOutput; +import com.lambdaworks.redis.output.MapOutput; +import com.lambdaworks.redis.output.MultiOutput; +import com.lambdaworks.redis.output.StatusOutput; +import com.lambdaworks.redis.output.ValueListOutput; +import com.lambdaworks.redis.output.ValueOutput; +import com.lambdaworks.redis.output.ValueSetOutput; import com.lambdaworks.redis.protocol.Command; import com.lambdaworks.redis.protocol.CommandArgs; +import com.lambdaworks.redis.protocol.CommandOutput; import com.lambdaworks.redis.protocol.CommandType; import com.lambdaworks.redis.pubsub.RedisPubSubConnection; @@ -71,6 +89,7 @@ import com.lambdaworks.redis.pubsub.RedisPubSubConnection; public class LettuceConnection implements RedisConnection { static final RedisCodec CODEC = new BytesRedisCodec(); + private static final TypeHints typeHints = new TypeHints(); private final com.lambdaworks.redis.RedisAsyncConnection asyncSharedConn; private final com.lambdaworks.redis.RedisConnection sharedConn; @@ -252,7 +271,23 @@ public class LettuceConnection implements RedisConnection { return getAsyncConnection().await(cmd, timeout, TimeUnit.MILLISECONDS); } + @Override public Object execute(String command, byte[]... args) { + return execute(command, null, args); + } + + /** + * 'Native' or 'raw' execution of the given command along-side the given arguments. + * + * @see RedisCommands#execute(String, byte[]...) + * @param command Command to execute + * @param commandOutputTypeHint Type of Output to use, may be (may be {@literal null}). + * @param args Possible command arguments (may be {@literal null}) + * @return execution result. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Object execute(String command, CommandOutput commandOutputTypeHint, byte[]... args) { + Assert.hasText(command, "a valid command needs to be specified"); try { String name = command.trim().toUpperCase(); @@ -263,16 +298,15 @@ public class LettuceConnection implements RedisConnection { cmdArg.addKeys(args); } + CommandOutput expectedOutput = commandOutputTypeHint != null ? commandOutputTypeHint : typeHints.getTypeHint(cmd); if (isPipelined()) { - pipeline(new LettuceResult(getAsyncConnection().dispatch(cmd, new ByteArrayOutput(CODEC), - cmdArg))); + pipeline(new LettuceResult(getAsyncConnection().dispatch(cmd, expectedOutput, cmdArg))); return null; } else if (isQueueing()) { - transaction(new LettuceResult(getAsyncConnection().dispatch(cmd, new ByteArrayOutput(CODEC), - cmdArg))); + transaction(new LettuceResult(getAsyncConnection().dispatch(cmd, expectedOutput, cmdArg))); return null; } else { - return await(getAsyncConnection().dispatch(cmd, new ByteArrayOutput(CODEC), cmdArg)); + return await(getAsyncConnection().dispatch(cmd, expectedOutput, cmdArg)); } } catch (RedisException ex) { throw convertLettuceAccessException(ex); @@ -2938,4 +2972,202 @@ public class LettuceConnection implements RedisConnection { return args; } + /** + * {@link TypeHints} provide {@link CommandOutput} information for a given {@link CommandType}. + * + * @since 1.2.1 + */ + static class TypeHints { + + @SuppressWarnings("rawtypes")// + private static final Map> COMMAND_OUTPUT_TYPE_MAPPING = new HashMap>(); + + @SuppressWarnings("rawtypes")// + private static final Map, Constructor> CONSTRUCTORS = new ConcurrentHashMap, Constructor>(); + + { + // INTEGER + COMMAND_OUTPUT_TYPE_MAPPING.put(BITCOUNT, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(BITOP, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(DBSIZE, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(DECR, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(DECRBY, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(DEL, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(GETBIT, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(HDEL, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(HINCRBY, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(HLEN, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(INCR, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(INCRBY, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LINSERT, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LLEN, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LPUSH, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LPUSHX, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LREM, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(PTTL, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(PUBLISH, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(RPUSH, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(RPUSHX, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SADD, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SCARD, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SDIFFSTORE, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SETBIT, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SETRANGE, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SINTERSTORE, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SREM, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SUNIONSTORE, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(STRLEN, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(TTL, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZADD, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZCOUNT, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZINTERSTORE, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZRANK, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZREM, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZREMRANGEBYRANK, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZREMRANGEBYSCORE, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZREVRANK, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZUNIONSTORE, IntegerOutput.class); + + // DOUBLE + COMMAND_OUTPUT_TYPE_MAPPING.put(HINCRBYFLOAT, DoubleOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(INCRBYFLOAT, DoubleOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(MGET, ValueListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZINCRBY, DoubleOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZSCORE, DoubleOutput.class); + + // MAP + COMMAND_OUTPUT_TYPE_MAPPING.put(HGETALL, MapOutput.class); + + // KEY LIST + COMMAND_OUTPUT_TYPE_MAPPING.put(HKEYS, KeyListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(KEYS, KeyListOutput.class); + + // KEY VALUE + COMMAND_OUTPUT_TYPE_MAPPING.put(BRPOP, KeyValueOutput.class); + + // SINGLE VALUE + COMMAND_OUTPUT_TYPE_MAPPING.put(BRPOPLPUSH, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ECHO, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(GET, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(GETRANGE, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(GETSET, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(HGET, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LINDEX, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LPOP, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(RANDOMKEY, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(RENAME, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(RPOP, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(RPOPLPUSH, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SPOP, ValueOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SRANDMEMBER, ValueOutput.class); + + // STATUS VALUE + COMMAND_OUTPUT_TYPE_MAPPING.put(BGREWRITEAOF, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(BGSAVE, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(CLIENT, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(DEBUG, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(DISCARD, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(FLUSHALL, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(FLUSHDB, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(HMSET, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(INFO, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LSET, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LTRIM, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(MIGRATE, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(MSET, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(QUIT, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(RESTORE, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SAVE, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SELECT, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SET, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SETEX, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SHUTDOWN, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SLAVEOF, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SYNC, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(TYPE, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(WATCH, StatusOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(UNWATCH, StatusOutput.class); + + // VALUE LIST + COMMAND_OUTPUT_TYPE_MAPPING.put(HMGET, ValueListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(MGET, ValueListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(HVALS, ValueListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(LRANGE, ValueListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SORT, ValueListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZRANGE, ValueListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZRANGEBYSCORE, ValueListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZREVRANGE, ValueListOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(ZREVRANGEBYSCORE, ValueListOutput.class); + + // BOOLEAN + COMMAND_OUTPUT_TYPE_MAPPING.put(EXISTS, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(EXPIRE, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(EXPIREAT, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(HEXISTS, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(HSET, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(HSETNX, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(MOVE, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(MSETNX, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(PERSIST, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(PEXPIRE, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(PEXPIREAT, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(RENAMENX, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SETNX, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SISMEMBER, BooleanOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SMOVE, BooleanOutput.class); + + // MULTI + COMMAND_OUTPUT_TYPE_MAPPING.put(EXEC, MultiOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(MULTI, MultiOutput.class); + + // DATE + COMMAND_OUTPUT_TYPE_MAPPING.put(LASTSAVE, DateOutput.class); + + // VALUE SET + COMMAND_OUTPUT_TYPE_MAPPING.put(SDIFF, ValueSetOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SINTER, ValueSetOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SMEMBERS, ValueSetOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(SUNION, ValueSetOutput.class); + } + + /** + * Returns the {@link CommandOutput} mapped for given {@link CommandType} or {@link ByteArrayOutput} as default. + * + * @param type + * @return {@link ByteArrayOutput} as default when no matching {@link CommandOutput} available. + */ + @SuppressWarnings("rawtypes") + public CommandOutput getTypeHint(CommandType type) { + return getTypeHint(type, new ByteArrayOutput(CODEC)); + } + + /** + * Returns the {@link CommandOutput} mapped for given {@link CommandType} given {@link CommandOutput} as default. + * + * @param type + * @return + */ + @SuppressWarnings("rawtypes") + public CommandOutput getTypeHint(CommandType type, CommandOutput defaultType) { + + if (type == null || !COMMAND_OUTPUT_TYPE_MAPPING.containsKey(type)) { + return defaultType; + } + CommandOutput outputType = instanciateCommandOutput(COMMAND_OUTPUT_TYPE_MAPPING.get(type)); + return outputType != null ? outputType : defaultType; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private CommandOutput instanciateCommandOutput(Class type) { + + Assert.notNull(type, "Cannot create instance for 'null' type."); + Constructor constructor = CONSTRUCTORS.get(type); + if (constructor == null) { + constructor = (Constructor) ClassUtils.getConstructorIfAvailable(type, RedisCodec.class); + CONSTRUCTORS.put(type, constructor); + } + return BeanUtils.instantiateClass(constructor, CODEC); + } + } + } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionIntegrationTests.java index 1bf0cb14c..3419df7b6 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionIntegrationTests.java @@ -19,13 +19,17 @@ package org.springframework.data.redis.connection.jedis; import static org.junit.Assert.*; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; +import org.hamcrest.core.AllOf; +import org.hamcrest.core.IsInstanceOf; import org.junit.After; import org.junit.Test; +import org.junit.internal.matchers.IsCollectionContaining; import org.junit.runner.RunWith; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.redis.SettingsUtils; @@ -313,4 +317,20 @@ public class JedisConnectionIntegrationTests extends AbstractConnectionIntegrati factory2.getConnection().dbSize(); factory2.destroy(); } + + /** + * @see DATAREDIS-285 + */ + @SuppressWarnings("unchecked") + @Test + public void testExecuteShouldConvertArrayReplyCorrectly() { + connection.set("spring", "awesome"); + connection.set("data", "cool"); + connection.set("redis", "supercalifragilisticexpialidocious"); + + assertThat( + connection.execute("MGET", "spring".getBytes(), "data".getBytes(), "redis".getBytes()), + AllOf.allOf(IsInstanceOf.instanceOf(List.class), IsCollectionContaining.hasItems("awesome".getBytes(), + "cool".getBytes(), "supercalifragilisticexpialidocious".getBytes()))); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java index 96ace9c12..e7326aa33 100644 --- a/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java @@ -21,13 +21,17 @@ import static org.junit.Assert.*; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import org.apache.commons.pool.impl.GenericObjectPool.Config; +import org.hamcrest.core.IsInstanceOf; import org.jredis.JRedis; import org.jredis.protocol.BulkResponse; +import org.jredis.ri.alphazero.protocol.SyncProtocol.SyncMultiBulkResponse; import org.junit.After; import org.junit.Ignore; import org.junit.Test; +import org.junit.internal.matchers.IsCollectionContaining; import org.junit.runner.RunWith; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.RedisConnectionFailureException; @@ -778,4 +782,23 @@ public class JRedisConnectionIntegrationTests extends AbstractConnectionIntegrat super.testGetTimeShouldRequestServerTime(); } + /** + * @see DATAREDIS-285 + */ + @Test + public void testExecuteShouldConvertArrayReplyCorrectly() { + connection.set("spring", "awesome"); + connection.set("data", "cool"); + connection.set("redis", "supercalifragilisticexpialidocious"); + + Object result = connection.execute("MGET", "spring".getBytes(), "data".getBytes(), "redis".getBytes()); + + assertThat(result, IsInstanceOf.instanceOf(SyncMultiBulkResponse.class)); + + List data = ((SyncMultiBulkResponse) result).getMultiBulkData(); + assertThat( + data, + IsCollectionContaining.hasItems("awesome".getBytes(), "cool".getBytes(), + "supercalifragilisticexpialidocious".getBytes())); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionIntegrationTests.java index 5b6c553db..eeaf4d218 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionIntegrationTests.java @@ -21,9 +21,13 @@ import static org.junit.Assume.*; import static org.springframework.data.redis.SpinBarrier.*; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import org.hamcrest.core.AllOf; +import org.hamcrest.core.IsInstanceOf; import org.junit.Test; +import org.junit.internal.matchers.IsCollectionContaining; import org.junit.runner.RunWith; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.RedisSystemException; @@ -276,4 +280,20 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra factory2.destroy(); } } + + /** + * @see DATAREDIS-285 + */ + @SuppressWarnings("unchecked") + @Test + public void testExecuteShouldConvertArrayReplyCorrectly() { + connection.set("spring", "awesome"); + connection.set("data", "cool"); + connection.set("redis", "supercalifragilisticexpialidocious"); + + assertThat( + connection.execute("MGET", "spring".getBytes(), "data".getBytes(), "redis".getBytes()), + AllOf.allOf(IsInstanceOf.instanceOf(List.class), IsCollectionContaining.hasItems("awesome".getBytes(), + "cool".getBytes(), "supercalifragilisticexpialidocious".getBytes()))); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionIntegrationTests.java index 497530ae7..0d6502643 100644 --- a/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionIntegrationTests.java @@ -18,7 +18,10 @@ package org.springframework.data.redis.connection.srp; import java.util.Arrays; +import org.hamcrest.core.Is; +import org.hamcrest.core.IsInstanceOf; import org.junit.After; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.redis.connection.AbstractConnectionIntegrationTests; @@ -27,6 +30,8 @@ import org.springframework.test.annotation.IfProfileValue; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import redis.reply.Reply; + /** * Integration test of {@link SrpConnection} * @@ -75,4 +80,23 @@ public class SrpConnectionIntegrationTests extends AbstractConnectionIntegration ReturnType.MULTI, 0)); verifyResults(Arrays.asList(new Object[] { Arrays.asList(new Object[] { "OK", "OK" }) })); } + + /** + * @see DATAREDIS-285 + */ + @Test + public void testExecuteShouldConvertArrayReplyCorrectly() { + connection.set("spring", "awesome"); + connection.set("data", "cool"); + connection.set("redis", "supercalifragilisticexpialidocious"); + + Object result = connection.execute("MGET", "spring".getBytes(), "data".getBytes(), "redis".getBytes()); + Assert.assertThat(result, IsInstanceOf.instanceOf(Reply[].class)); + + Reply[] replies = (Reply[]) result; + + Assert.assertThat(replies[0].data(), Is. is("awesome".getBytes())); + Assert.assertThat(replies[1].data(), Is. is("cool".getBytes())); + Assert.assertThat(replies[2].data(), Is. is("supercalifragilisticexpialidocious".getBytes())); + } }