diff --git a/gradle.properties b/gradle.properties index f09fb8eb53..7c87a7ee3a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ jredisVersion=06052013 jedisVersion=2.4.1 springVersion=3.2.8.RELEASE log4jVersion=1.2.17 -version=1.3.0.BUILD-SNAPSHOT +version=1.3.0.DATAREDIS-285-SNAPSHOT srpVersion=0.7 jacksonVersion=1.8.8 fasterXmlJacksonVersion=2.2.0 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 7f6680cb30..98044a813c 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 @@ 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.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.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 @@ 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 @@ private Object await(Command cmd) { 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 Object execute(String command, byte[]... args) { 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 @@ private ZStoreArgs zStoreArgs(Aggregate aggregate, int[] weights) { 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 1bf0cb14c1..3419df7b69 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 @@ 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 void testPoolNPE() { 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 96ace9c12c..e7326aa33f 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 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 void testGetTimeShouldRequestServerTime() { 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 5b6c553db9..eeaf4d218e 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.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 void testMove() { 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 497530ae78..0d65026433 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 @@ 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.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import redis.reply.Reply; + /** * Integration test of {@link SrpConnection} * @@ -75,4 +80,23 @@ public void testEvalReturnArrayOKs() { 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())); + } }