DATAREDIS-316 - Add EX/PX, NX/XX options to SET command.

We now support EX/PX and NX/XX arguments along with the SET command for both xetorthio/jedis and mp911de/lettuce.

Jedis 2.8 does not support all combinations of EX/PX and NX/XX. To work around those limitations we delegate to the according set methods to execute related commands or fail fast if there is no way for delegation.

Original pull request: #170.
This commit is contained in:
Christoph Strobl
2016-02-15 15:25:05 +01:00
committed by Mark Paluch
parent 68da4a4ee5
commit 0c6c127b5e
18 changed files with 1239 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 the original author or authors.
* Copyright 2011-2016 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.
@@ -36,6 +36,7 @@ import org.springframework.data.redis.connection.convert.SetConverter;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@@ -730,6 +731,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection {
delegate.set(key, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOptions)
*/
@Override
public void set(byte[] key, byte[] value, Expiration expiration, SetOption option) {
delegate.set(key, value, expiration, option);
}
public Boolean setBit(byte[] key, long offset, boolean value) {
return delegate.setBit(key, offset, value);
}
@@ -1860,6 +1870,14 @@ public class DefaultStringRedisConnection implements StringRedisConnection {
delegate.set(serialize(key), serialize(value));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#set(java.lang.String, java.lang.String, org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOptions)
*/
public void set(String key, String value, Expiration expiration, SetOption option) {
set(serialize(key), serialize(value), expiration, option);
}
public Boolean setBit(String key, long offset, boolean value) {
return delegate.setBit(serialize(key), offset, value);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2016 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.
@@ -18,6 +18,8 @@ package org.springframework.data.redis.connection;
import java.util.List;
import java.util.Map;
import org.springframework.data.redis.core.types.Expiration;
/**
* String/Value-specific commands supported by Redis.
*
@@ -71,6 +73,18 @@ public interface RedisStringCommands {
*/
void set(byte[] key, byte[] value);
/**
* Set {@code value} for {@code key} applying timeouts from {@code expiration} if set and inserting/updating values
* depending on {@code options}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @param expiration can be {@literal null}. Defaulted to {@link Expiration#persistent()}.
* @param option can be {@literal null}. Defaulted to {@link SetOption#UPSERT}.
* @since 1.7
*/
void set(byte[] key, byte[] value, Expiration expiration, SetOption option);
/**
* Set {@code value} for {@code key}, only if {@code key} does not exist.
* <p>
@@ -278,4 +292,61 @@ public interface RedisStringCommands {
* @return
*/
Long strLen(byte[] key);
/**
* {@code SET} command arguments for {@code NX}, {@code XX}.
*
* @author Christoph Strobl
* @since 1.7
*/
public static enum SetOption {
/**
* Do not set any additional command argument.
*
* @return
*/
UPSERT,
/**
* {@code NX}
*
* @return
*/
SET_IF_ABSENT,
/**
* {@code XX}
*
* @return
*/
SET_IF_PRESENT;
/**
* Do not set any additional command argument.
*
* @return
*/
public static SetOption upsert() {
return UPSERT;
}
/**
* {@code XX}
*
* @return
*/
public static SetOption ifPresent() {
return SET_IF_PRESENT;
}
/**
* {@code NX}
*
* @return
*/
public static SetOption ifAbsent() {
return SET_IF_ABSENT;
}
}
}

View File

@@ -24,6 +24,7 @@ import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.RedisSerializer;
@@ -95,6 +96,18 @@ public interface StringRedisConnection extends RedisConnection {
void set(String key, String value);
/**
* Set {@code value} for {@code key} applying timeouts from {@code expiration} if set and inserting/updating values
* depending on {@code options}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @param expiration can be {@literal null}. Defaulted to {@link Expiration#persistent()}.
* @param option can be {@literal null}. Defaulted to {@link SetOption#UPSERT}.
* @since 1.7
*/
void set(String key, String value, Expiration expiration, SetOption option);
Boolean setNX(String key, String value);
void setEx(String key, long seconds, String value);

View File

@@ -28,6 +28,7 @@ import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.dao.DataAccessException;
@@ -61,10 +62,11 @@ import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.ObjectUtils;
import redis.clients.jedis.BinaryJedisPubSub;
import redis.clients.jedis.Jedis;
@@ -249,7 +251,8 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public byte[] randomKey() {
List<RedisClusterNode> nodes = new ArrayList<RedisClusterNode>(topologyProvider.getTopology().getActiveMasterNodes());
List<RedisClusterNode> nodes = new ArrayList<RedisClusterNode>(topologyProvider.getTopology()
.getActiveMasterNodes());
Set<RedisNode> inspectedNodes = new HashSet<RedisNode>(nodes.size());
do {
@@ -594,6 +597,49 @@ public class JedisClusterConnection implements RedisClusterConnection {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOptions)
*/
@Override
public void set(byte[] key, byte[] value, Expiration expiration, SetOption option) {
if (expiration == null || expiration.isPersitent()) {
if (option == null || ObjectUtils.nullSafeEquals(SetOption.UPSERT, option)) {
set(key, value);
} else {
// BinaryCluster does not support set with nxxx and binary key/value pairs.
if (ObjectUtils.nullSafeEquals(SetOption.SET_IF_PRESENT, option)) {
throw new UnsupportedOperationException("Jedis does not support SET XX without PX or EX on BinaryCluster.");
}
setNX(key, value);
}
} else {
if (option == null || ObjectUtils.nullSafeEquals(SetOption.UPSERT, option)) {
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
pSetEx(key, expiration.getExpirationTime(), value);
} else {
setEx(key, expiration.getExpirationTime(), value);
}
} else {
byte[] nxxx = JedisConverters.toSetCommandNxXxArgument(option);
byte[] expx = JedisConverters.toSetCommandExPxArgument(expiration);
try {
cluster.set(key, value, nxxx, expx, expiration.getExpirationTime());
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisStringCommands#setNX(byte[], byte[])
@@ -3432,7 +3478,8 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public void clusterForget(final RedisClusterNode node) {
Set<RedisClusterNode> nodes = new LinkedHashSet<RedisClusterNode>(topologyProvider.getTopology().getActiveMasterNodes());
Set<RedisClusterNode> nodes = new LinkedHashSet<RedisClusterNode>(topologyProvider.getTopology()
.getActiveMasterNodes());
final RedisClusterNode nodeToRemove = topologyProvider.getTopology().lookup(node);
nodes.remove(nodeToRemove);
@@ -3536,8 +3583,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
Assert.notNull(master, "Master cannot be null!");
final RedisClusterNode nodeToUse = topologyProvider.getTopology()
.lookup(master);
final RedisClusterNode nodeToUse = topologyProvider.getTopology().lookup(master);
return JedisConverters.toSetOfRedisClusterNodes(clusterCommandExecutor.executeCommandOnSingleNode(
new JedisClusterCommandCallback<List<String>>() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 the original author or authors.
* Copyright 2011-2016 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.
@@ -54,6 +54,7 @@ import org.springframework.data.redis.core.KeyBoundCursor;
import org.springframework.data.redis.core.ScanCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -1139,6 +1140,87 @@ public class JedisConnection extends AbstractRedisConnection {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOption)
*/
@Override
public void set(byte[] key, byte[] value, Expiration expiration, SetOption option) {
if (expiration == null || expiration.isPersitent()) {
if (option == null || ObjectUtils.nullSafeEquals(SetOption.UPSERT, option)) {
set(key, value);
} else {
try {
byte[] nxxx = JedisConverters.toSetCommandNxXxArgument(option);
if (isPipelined()) {
pipeline(new JedisStatusResult(pipeline.set(key, value, nxxx)));
return;
}
if (isQueueing()) {
transaction(new JedisStatusResult(transaction.set(key, value, nxxx)));
return;
}
jedis.set(key, value, nxxx);
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
} else {
if (option == null || ObjectUtils.nullSafeEquals(SetOption.UPSERT, option)) {
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
pSetEx(key, expiration.getExpirationTime(), value);
} else {
setEx(key, expiration.getExpirationTime(), value);
}
} else {
byte[] nxxx = JedisConverters.toSetCommandNxXxArgument(option);
byte[] expx = JedisConverters.toSetCommandExPxArgument(expiration);
try {
if (isPipelined()) {
if (expiration.getExpirationTime() > Integer.MAX_VALUE) {
throw new IllegalArgumentException(
"Expiration.exprirationTime must be less than equals Integer.MAX_VALUE for pipeline in Jedis.");
}
pipeline(new JedisStatusResult(pipeline.set(key, value, nxxx, expx, (int) expiration.getExpirationTime())));
return;
}
if (isQueueing()) {
if (expiration.getExpirationTime() > Integer.MAX_VALUE) {
throw new IllegalArgumentException(
"Expiration.exprirationTime must be less than equals Integer.MAX_VALUE for transactions in Jedis.");
}
transaction(new JedisStatusResult(transaction.set(key, value, nxxx, expx,
(int) expiration.getExpirationTime())));
return;
}
jedis.set(key, value, nxxx, expx, expiration.getExpirationTime());
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
}
}
public byte[] getSet(byte[] key, byte[] value) {
try {
if (isPipelined()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2016 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,11 +17,11 @@ package org.springframework.data.redis.connection.jedis;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
@@ -29,7 +29,9 @@ import org.springframework.data.redis.connection.DefaultTuple;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.RedisZSetCommands.Range.Boundary;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.SortParameters;
@@ -40,9 +42,11 @@ import org.springframework.data.redis.connection.convert.ListConverter;
import org.springframework.data.redis.connection.convert.MapConverter;
import org.springframework.data.redis.connection.convert.SetConverter;
import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import redis.clients.jedis.BinaryClient.LIST_POSITION;
@@ -71,11 +75,17 @@ abstract public class JedisConverters extends Converters {
private static final Converter<redis.clients.jedis.Tuple, Tuple> TUPLE_CONVERTER;
private static final ListConverter<redis.clients.jedis.Tuple, Tuple> TUPLE_LIST_TO_TUPLE_LIST_CONVERTER;
private static final Converter<Object, RedisClusterNode> OBJECT_TO_CLUSTER_NODE_CONVERTER;
private static final Converter<Expiration, byte[]> EXPIRATION_TO_COMMAND_OPTION_CONVERTER;
private static final Converter<SetOption, byte[]> SET_OPTION_TO_COMMAND_OPTION_CONVERTER;
public static final byte[] PLUS_BYTES;
public static final byte[] MINUS_BYTES;
public static final byte[] POSITIVE_INFINITY_BYTES;
public static final byte[] NEGATIVE_INFINITY_BYTES;
private static final byte[] EX;
private static final byte[] PX;
private static final byte[] NX;
private static final byte[] XX;
static {
@@ -113,6 +123,46 @@ abstract public class JedisConverters extends Converters {
((Number) nodeInfo.get(1)).intValue(), range);
}
};
EX = toBytes("EX");
PX = toBytes("PX");
EXPIRATION_TO_COMMAND_OPTION_CONVERTER = new Converter<Expiration, byte[]>() {
@Override
public byte[] convert(Expiration source) {
if (source == null || source.isPersitent()) {
return new byte[0];
}
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, source.getTimeUnit())) {
return PX;
}
return EX;
}
};
NX = toBytes("NX");
XX = toBytes("XX");
SET_OPTION_TO_COMMAND_OPTION_CONVERTER = new Converter<RedisStringCommands.SetOption, byte[]>() {
@Override
public byte[] convert(SetOption source) {
switch (source) {
case UPSERT:
return new byte[0];
case SET_IF_ABSENT:
return NX;
case SET_IF_PRESENT:
return XX;
}
throw new IllegalArgumentException(String.format("Invalid argument %s for SetOption.", source));
}
};
}
public static Converter<String, byte[]> stringToBytes() {
@@ -322,6 +372,42 @@ abstract public class JedisConverters extends Converters {
return boundaryToBytes(boundary, toBytes("["), toBytes("("));
}
/**
* Converts a given {@link Expiration} to the according {@code SET} command argument.<br />
* <dl>
* <dt>{@link TimeUnit#SECONDS}</dt>
* <dd>{@code EX}</dd>
* <dt>{@link TimeUnit#MILLISECONDS}</dt>
* <dd>{@code PX}</dd>
* </dl>
*
* @param expiration
* @return
* @since 1.7
*/
public static byte[] toSetCommandExPxArgument(Expiration expiration) {
return EXPIRATION_TO_COMMAND_OPTION_CONVERTER.convert(expiration);
}
/**
* Converts a given {@link SetOption} to the according {@code SET} command argument.<br />
* <dl>
* <dt>{@link SetOption#UPSERT}</dt>
* <dd>{@code byte[0]}</dd>
* <dt>{@link SetOption#SET_IF_ABSENT}</dt>
* <dd>{@code NX}</dd>
* <dt>{@link SetOption#SET_IF_PRESENT}</dt>
* <dd>{@code XX}</dd>
* </dl>
*
* @param option
* @return
* @since 1.7
*/
public static byte[] toSetCommandNxXxArgument(SetOption option) {
return SET_OPTION_TO_COMMAND_OPTION_CONVERTER.convert(option);
}
private static byte[] boundaryToBytes(Boundary boundary, byte[] inclPrefix, byte[] exclPrefix) {
byte[] prefix = boundary.isIncluding() ? inclPrefix : exclPrefix;

View File

@@ -47,6 +47,7 @@ import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.connection.Subscription;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -470,6 +471,16 @@ public class JredisConnection extends AbstractRedisConnection {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOption)
*/
@Override
public void set(byte[] key, byte[] value, Expiration expiration, SetOption option) {
throw new UnsupportedOperationException(
"SET with options is not supported for JRedis. Please use SETNX, SETEX, PSETEX.");
}
public byte[] getSet(byte[] key, byte[] value) {
try {
return jredis.getset(key, value);

View File

@@ -62,6 +62,7 @@ import org.springframework.data.redis.core.RedisCommand;
import org.springframework.data.redis.core.ScanCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -1218,6 +1219,30 @@ public class LettuceConnection extends AbstractRedisConnection {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOption)
*/
@Override
public void set(byte[] key, byte[] value, Expiration expiration, SetOption option) {
try {
if (isPipelined()) {
pipeline(new LettuceStatusResult(getAsyncConnection().set(key, value,
LettuceConverters.toSetArgs(expiration, option))));
return;
}
if (isQueueing()) {
transaction(new LettuceTxStatusResult(getConnection().set(key, value,
LettuceConverters.toSetArgs(expiration, option))));
return;
}
getConnection().set(key, value, LettuceConverters.toSetArgs(expiration, option));
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
public byte[] getSet(byte[] key, byte[] value) {
try {
if (isPipelined()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2016 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.
@@ -26,6 +26,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
@@ -39,6 +40,7 @@ import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisNode.NodeType;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.RedisZSetCommands.Range.Boundary;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.ReturnType;
@@ -47,6 +49,7 @@ import org.springframework.data.redis.connection.SortParameters.Order;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.connection.convert.LongToBooleanConverter;
import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -60,6 +63,7 @@ import com.lambdaworks.redis.SortArgs;
import com.lambdaworks.redis.cluster.models.partitions.Partitions;
import com.lambdaworks.redis.cluster.models.partitions.RedisClusterNode.NodeFlag;
import com.lambdaworks.redis.protocol.LettuceCharsets;
import com.lambdaworks.redis.protocol.SetArgs;
/**
* Lettuce type converters
@@ -614,4 +618,43 @@ abstract public class LettuceConverters extends Converters {
return CLUSTER_NODE_TO_CLUSTER_NODE_CONVERTER.convert(source);
}
/**
* Converts a given {@link Expiration} and {@link SetOption} to the according {@link SetArgs}.<br />
*
* @param expiration can be {@literal null}.
* @param option can be {@literal null}.
* @since 1.7
*/
public static SetArgs toSetArgs(Expiration expiration, SetOption option) {
SetArgs args = new SetArgs();
if (expiration != null && !expiration.isPersitent()) {
switch (expiration.getTimeUnit()) {
case SECONDS:
args.ex(expiration.getExpirationTime());
break;
default:
args.px(expiration.getConverted(TimeUnit.MILLISECONDS));
break;
}
}
if (option != null) {
switch (option) {
case SET_IF_ABSENT:
args.nx();
break;
case SET_IF_PRESENT:
args.xx();
break;
default:
break;
}
}
return args;
}
}

View File

@@ -46,6 +46,7 @@ import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.connection.Subscription;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
@@ -875,6 +876,16 @@ public class SrpConnection extends AbstractRedisConnection {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOption)
*/
@Override
public void set(byte[] key, byte[] value, Expiration expiration, SetOption option) {
throw new UnsupportedOperationException(
"SET with options is not supported for Srp. Please use SETNX, SETEX, PSETEX.");
}
public byte[] getSet(byte[] key, byte[] value) {
try {
if (isPipelined()) {

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.core.types;
import java.util.concurrent.TimeUnit;
import org.springframework.util.Assert;
/**
* Expiration holds a value with its associated {@link TimeUnit}.
*
* @author Christoph Strobl
* @since 1.7
*/
public class Expiration {
private long expirationTime;
private TimeUnit timeUnit;
/**
* Creates new {@link Expiration}.
*
* @param expirationTime can be {@literal null}. Defaulted to {@link TimeUnit#SECONDS}
* @param timeUnit
*/
protected Expiration(long expirationTime, TimeUnit timeUnit) {
this.expirationTime = expirationTime;
this.timeUnit = timeUnit != null ? timeUnit : TimeUnit.SECONDS;
}
/**
* Get the expiration time converted into {@link TimeUnit#MILLISECONDS}.
*
* @return
*/
public long getExpirationTimeInMilliseconds() {
return getConverted(TimeUnit.MILLISECONDS);
}
/**
* Get the expiration time converted into {@link TimeUnit#SECONDS}.
*
* @return
*/
public long getExpirationTimeInSeconds() {
return getConverted(TimeUnit.SECONDS);
}
/**
* Get the expiration time.
*
* @return
*/
public long getExpirationTime() {
return expirationTime;
}
/**
* Get the time unit for the expiration time.
*
* @return
*/
public TimeUnit getTimeUnit() {
return this.timeUnit;
}
/**
* Get the expiration time converted into the desired {@code targetTimeUnit}.
*
* @param targetTimeUnit must not {@literal null}.
* @return
* @throws IllegalArgumentException
*/
public long getConverted(TimeUnit targetTimeUnit) {
Assert.notNull(targetTimeUnit, "TargetTimeUnit must not be null!");
return targetTimeUnit.convert(expirationTime, timeUnit);
}
/**
* Creates new {@link Expiration} with {@link TimeUnit#SECONDS}.
*
* @param expirationTime
* @return
*/
public static Expiration seconds(long expirationTime) {
return new Expiration(expirationTime, TimeUnit.SECONDS);
}
/**
* Creates new {@link Expiration} with {@link TimeUnit#MILLISECONDS}.
*
* @param expirationTime
* @return
*/
public static Expiration milliseconds(long expirationTime) {
return new Expiration(expirationTime, TimeUnit.MILLISECONDS);
}
/**
* Creates new persistent {@link Expiration}.
*
* @return
*/
public static Expiration persistent() {
return new Expiration(-1, TimeUnit.SECONDS);
}
/**
* @return {@literal true} if {@link Expiration} is set to persistent.
*/
public boolean isPersitent() {
return expirationTime == -1;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 the original author or authors.
* Copyright 2011-2016 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.connection;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.number.IsCloseTo.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.springframework.data.redis.SpinBarrier.*;
@@ -52,6 +53,7 @@ import org.springframework.data.redis.RedisVersionUtils;
import org.springframework.data.redis.TestCondition;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
@@ -60,6 +62,7 @@ import org.springframework.data.redis.connection.StringRedisConnection.StringTup
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
@@ -2220,6 +2223,282 @@ public abstract class AbstractConnectionIntegrationTests {
assertThat(values, not(hasItems("a", "b", "c", "d")));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithExpirationAndNullOpionShouldSetTtlWhenKeyDoesNotExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "foo", Expiration.milliseconds(500), null);
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(500d, 499d)));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithExpirationAndUpsertOpionShouldSetTtlWhenKeyDoesNotExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "foo", Expiration.milliseconds(500), SetOption.upsert());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(500d, 499d)));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithExpirationAndUpsertOpionShouldSetTtlWhenKeyDoesExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "spring");
connection.set(key, "data", Expiration.milliseconds(500), SetOption.upsert());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(500d, 499d)));
assertThat(((String) result.get(2)), is(equalTo("data")));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithExpirationAndAbsentOptionShouldSetTtlWhenKeyDoesExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "spring");
connection.set(key, "data", Expiration.milliseconds(500), SetOption.ifAbsent());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(-1, 0)));
assertThat(((String) result.get(2)), is(equalTo("spring")));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithExpirationAndAbsentOptionShouldSetTtlWhenKeyDoesNotExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "data", Expiration.milliseconds(500), SetOption.ifAbsent());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(500d, 499d)));
assertThat(((String) result.get(2)), is(equalTo("data")));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithExpirationAndPresentOptionShouldSetTtlWhenKeyDoesExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "spring");
connection.set(key, "data", Expiration.milliseconds(500), SetOption.ifPresent());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(500, 499)));
assertThat(((String) result.get(2)), is(equalTo("data")));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithExpirationAndPresentOptionShouldSetTtlWhenKeyDoesNotExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "data", Expiration.milliseconds(500), SetOption.ifPresent());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.FALSE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(-2, 0)));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithNullExpirationAndUpsertOpionShouldSetTtlWhenKeyDoesNotExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "foo", null, SetOption.upsert());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(-1, 0)));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithoutExpirationAndUpsertOpionShouldSetTtlWhenKeyDoesNotExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "foo", Expiration.persistent(), SetOption.upsert());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(-1, 0)));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithoutExpirationAndUpsertOpionShouldSetTtlWhenKeyDoesExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "spring");
connection.set(key, "data", Expiration.persistent(), SetOption.upsert());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(-1, 0)));
assertThat(((String) result.get(2)), is(equalTo("data")));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithoutExpirationAndAbsentOptionShouldSetTtlWhenKeyDoesExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "spring");
connection.set(key, "data", Expiration.persistent(), SetOption.ifAbsent());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(-1, 0)));
assertThat(((String) result.get(2)), is(equalTo("spring")));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithoutExpirationAndAbsentOptionShouldSetTtlWhenKeyDoesNotExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "data", Expiration.persistent(), SetOption.ifAbsent());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(-1, 0)));
assertThat(((String) result.get(2)), is(equalTo("data")));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithoutExpirationAndPresentOptionShouldSetTtlWhenKeyDoesExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "spring");
connection.set(key, "data", Expiration.persistent(), SetOption.ifPresent());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.TRUE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(-1, 0)));
assertThat(((String) result.get(2)), is(equalTo("data")));
}
/**
* @see DATAREDIS-316
*/
@Test
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
public void setWithoutExpirationAndPresentOptionShouldSetTtlWhenKeyDoesNotExist() {
String key = "exp-" + UUID.randomUUID();
connection.set(key, "data", Expiration.persistent(), SetOption.ifPresent());
actual.add(connection.exists(key));
actual.add(connection.pTtl(key));
actual.add(connection.get(key));
List<Object> result = getResults();
assertThat((Boolean) result.get(0), is(Boolean.FALSE));
assertThat(((Long) result.get(1)).doubleValue(), is(closeTo(-2, 0)));
}
protected void verifyResults(List<Object> expected) {
assertEquals(expected, getResults());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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,6 +15,9 @@
*/
package org.springframework.data.redis.connection;
/**
* @author Christoph Strobl
*/
public interface ClusterConnectionTests {
/**
@@ -893,4 +896,44 @@ public interface ClusterConnectionTests {
*/
void clusterGetMasterSlaveMapShouldListMastersAndSlavesCorrectly();
/**
* @see DATAREDIS-316
*/
void setWithExpirationInSecondsShouldWorkCorrectly();
/**
* @see DATAREDIS-316
*/
void setWithExpirationInMillisecondsShouldWorkCorrectly();
/**
* @see DATAREDIS-316
*/
void setWithOptionIfPresentShouldWorkCorrectly();
/**
* @see DATAREDIS-316
*/
void setWithOptionIfAbsentShouldWorkCorrectly();
/**
* @see DATAREDIS-316
*/
void setWithExpirationAndIfAbsentShouldWorkCorrectly();
/**
* @see DATAREDIS-316
*/
void setWithExpirationAndIfAbsentShouldNotBeAppliedWhenKeyExists();
/**
* @see DATAREDIS-316
*/
void setWithExpirationAndIfPresentShouldWorkCorrectly();
/**
* @see DATAREDIS-316
*/
void setWithExpirationAndIfPresentShouldNotBeAppliedWhenKeyDoesNotExists();
}

View File

@@ -30,6 +30,7 @@ import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisNode.RedisNodeBuilder;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.ObjectUtils;
@@ -891,5 +892,10 @@ public class RedisConnectionUnitTests {
public void migrate(byte[] key, RedisNode target, int dbIndex, MigrateOption option, long timeout) {
delegate.migrate(key, target, dbIndex, option, timeout);
}
@Override
public void set(byte[] key, byte[] value, Expiration expiration, SetOption options) {
delegate.set(key, value, expiration, options);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.connection.jedis;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.number.IsCloseTo.*;
import static org.junit.Assert.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
@@ -46,10 +47,12 @@ import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.test.util.RedisClusterRule;
import redis.clients.jedis.HostAndPort;
@@ -2308,4 +2311,103 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
assertThat(masterSlaveMap.get(new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_2_PORT)).isEmpty(), is(true));
assertThat(masterSlaveMap.get(new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_3_PORT)).isEmpty(), is(true));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationInSecondsShouldWorkCorrectly() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.seconds(1), SetOption.upsert());
assertThat(nativeConnection.exists(KEY_1_BYTES), is(true));
assertThat(nativeConnection.ttl(KEY_1_BYTES), is(1L));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationInMillisecondsShouldWorkCorrectly() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.milliseconds(500), SetOption.upsert());
assertThat(nativeConnection.exists(KEY_1_BYTES), is(true));
assertThat(nativeConnection.pttl(KEY_1).doubleValue(), is(closeTo(500d, 499d)));
}
/**
* @see DATAREDIS-316
*/
@Test(expected = UnsupportedOperationException.class)
public void setWithOptionIfPresentShouldWorkCorrectly() {
nativeConnection.set(KEY_1_BYTES, VALUE_1_BYTES);
clusterConnection.set(KEY_1_BYTES, VALUE_2_BYTES, Expiration.persistent(), SetOption.ifPresent());
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithOptionIfAbsentShouldWorkCorrectly() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.persistent(), SetOption.ifAbsent());
assertThat(nativeConnection.exists(KEY_1_BYTES), is(true));
assertThat(nativeConnection.ttl(KEY_1_BYTES), is(-1L));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationAndIfAbsentShouldWorkCorrectly() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.seconds(1), SetOption.ifAbsent());
assertThat(nativeConnection.exists(KEY_1_BYTES), is(true));
assertThat(nativeConnection.ttl(KEY_1_BYTES), is(1L));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationAndIfAbsentShouldNotBeAppliedWhenKeyExists() {
nativeConnection.set(KEY_1_BYTES, VALUE_1_BYTES);
clusterConnection.set(KEY_1_BYTES, VALUE_2_BYTES, Expiration.seconds(1), SetOption.ifAbsent());
assertThat(nativeConnection.exists(KEY_1_BYTES), is(true));
assertThat(nativeConnection.ttl(KEY_1_BYTES), is(-1L));
assertThat(nativeConnection.get(KEY_1_BYTES), is(equalTo(VALUE_1_BYTES)));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationAndIfPresentShouldWorkCorrectly() {
nativeConnection.set(KEY_1_BYTES, VALUE_1_BYTES);
clusterConnection.set(KEY_1_BYTES, VALUE_2_BYTES, Expiration.seconds(1), SetOption.ifPresent());
assertThat(nativeConnection.exists(KEY_1_BYTES), is(true));
assertThat(nativeConnection.ttl(KEY_1_BYTES), is(1L));
assertThat(nativeConnection.get(KEY_1_BYTES), is(equalTo(VALUE_2_BYTES)));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationAndIfPresentShouldNotBeAppliedWhenKeyDoesNotExists() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.seconds(1), SetOption.ifPresent());
assertThat(nativeConnection.exists(KEY_1_BYTES), is(false));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -29,7 +29,9 @@ import java.util.Map;
import org.junit.Test;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
/**
@@ -257,6 +259,56 @@ public class JedisConvertersUnitTests {
JedisConverters.boundaryToBytesForZRange(Range.range().gt(new Date()).getMin(), null);
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetCommandExPxOptionShouldReturnEXforSeconds() {
assertThat(JedisConverters.toSetCommandExPxArgument(Expiration.seconds(100)), equalTo(JedisConverters.toBytes("EX")));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetCommandExPxOptionShouldReturnEXforMilliseconds() {
assertThat(JedisConverters.toSetCommandExPxArgument(Expiration.milliseconds(100)),
equalTo(JedisConverters.toBytes("PX")));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetCommandExPxOptionShouldReturnEmptyArrayForNull() {
assertThat(JedisConverters.toSetCommandExPxArgument(null), equalTo(new byte[] {}));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetCommandNxXxOptionShouldReturnNXforAbsent() {
assertThat(JedisConverters.toSetCommandNxXxArgument(SetOption.ifAbsent()), equalTo(JedisConverters.toBytes("NX")));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetCommandNxXxOptionShouldReturnXXforAbsent() {
assertThat(JedisConverters.toSetCommandNxXxArgument(SetOption.ifPresent()), equalTo(JedisConverters.toBytes("XX")));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetCommandNxXxOptionShouldReturnEmptyArrayforUpsert() {
assertThat(JedisConverters.toSetCommandNxXxArgument(SetOption.upsert()), equalTo(new byte[] {}));
}
private void verifyRedisServerInfo(RedisServer server, Map<String, String> values) {
for (Map.Entry<String, String> entry : values.entrySet()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.connection.lettuce;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.number.IsCloseTo.*;
import static org.junit.Assert.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
@@ -44,11 +45,13 @@ import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.jedis.JedisConverters;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.test.util.RedisClusterRule;
import com.lambdaworks.redis.RedisURI.Builder;
@@ -2298,4 +2301,105 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
assertThat(masterSlaveMap.get(new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_2_PORT)).isEmpty(), is(true));
assertThat(masterSlaveMap.get(new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_3_PORT)).isEmpty(), is(true));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationInSecondsShouldWorkCorrectly() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.seconds(1), SetOption.upsert());
assertThat(nativeConnection.exists(KEY_1), is(true));
assertThat(nativeConnection.ttl(KEY_1), is(1L));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationInMillisecondsShouldWorkCorrectly() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.milliseconds(500), SetOption.upsert());
assertThat(nativeConnection.exists(KEY_1), is(true));
assertThat(nativeConnection.pttl(KEY_1).doubleValue(), is(closeTo(500d, 499d)));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithOptionIfPresentShouldWorkCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
clusterConnection.set(KEY_1_BYTES, VALUE_2_BYTES, Expiration.persistent(), SetOption.ifPresent());
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithOptionIfAbsentShouldWorkCorrectly() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.persistent(), SetOption.ifAbsent());
assertThat(nativeConnection.exists(KEY_1), is(true));
assertThat(nativeConnection.ttl(KEY_1), is(-1L));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationAndIfAbsentShouldWorkCorrectly() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.seconds(1), SetOption.ifAbsent());
assertThat(nativeConnection.exists(KEY_1), is(true));
assertThat(nativeConnection.ttl(KEY_1), is(1L));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationAndIfAbsentShouldNotBeAppliedWhenKeyExists() {
nativeConnection.set(KEY_1, VALUE_1);
clusterConnection.set(KEY_1_BYTES, VALUE_2_BYTES, Expiration.seconds(1), SetOption.ifAbsent());
assertThat(nativeConnection.exists(KEY_1), is(true));
assertThat(nativeConnection.ttl(KEY_1), is(-1L));
assertThat(nativeConnection.get(KEY_1), is(equalTo(VALUE_1)));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationAndIfPresentShouldWorkCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
clusterConnection.set(KEY_1_BYTES, VALUE_2_BYTES, Expiration.seconds(1), SetOption.ifPresent());
assertThat(nativeConnection.exists(KEY_1), is(true));
assertThat(nativeConnection.ttl(KEY_1), is(1L));
assertThat(nativeConnection.get(KEY_1), is(equalTo(VALUE_2)));
}
/**
* @see DATAREDIS-316
*/
@Test
public void setWithExpirationAndIfPresentShouldNotBeAppliedWhenKeyDoesNotExists() {
clusterConnection.set(KEY_1_BYTES, VALUE_1_BYTES, Expiration.seconds(1), SetOption.ifPresent());
assertThat(nativeConnection.exists(KEY_1), is(false));
}
}

View File

@@ -21,6 +21,7 @@ import static org.hamcrest.core.IsEqual.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import static org.springframework.test.util.ReflectionTestUtils.*;
import java.util.Arrays;
import java.util.Collections;
@@ -31,11 +32,14 @@ import org.junit.Test;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisClusterNode.Flag;
import org.springframework.data.redis.connection.RedisClusterNode.LinkState;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.cluster.models.partitions.Partitions;
import com.lambdaworks.redis.cluster.models.partitions.RedisClusterNode.NodeFlag;
import com.lambdaworks.redis.protocol.SetArgs;
/**
* @author Christoph Strobl
@@ -111,4 +115,102 @@ public class LettuceConvertersUnitTests {
assertThat(node.getLinkState(), is(LinkState.CONNECTED));
assertThat(node.getSlotRange().getSlots(), hasItems(1, 2, 3, 4, 5));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetArgsShouldReturnEmptyArgsForNullValues() {
SetArgs args = LettuceConverters.toSetArgs(null, null);
assertThat(getField(args, "ex"), is(nullValue()));
assertThat(getField(args, "px"), is(nullValue()));
assertThat((Boolean) getField(args, "nx"), is(Boolean.FALSE));
assertThat((Boolean) getField(args, "xx"), is(Boolean.FALSE));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetArgsShouldNotSetExOrPxForPersistent() {
SetArgs args = LettuceConverters.toSetArgs(Expiration.persistent(), null);
assertThat(getField(args, "ex"), is(nullValue()));
assertThat(getField(args, "px"), is(nullValue()));
assertThat((Boolean) getField(args, "nx"), is(Boolean.FALSE));
assertThat((Boolean) getField(args, "xx"), is(Boolean.FALSE));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetArgsShouldSetExForSeconds() {
SetArgs args = LettuceConverters.toSetArgs(Expiration.seconds(10), null);
assertThat((Long) getField(args, "ex"), is(10L));
assertThat(getField(args, "px"), is(nullValue()));
assertThat((Boolean) getField(args, "nx"), is(Boolean.FALSE));
assertThat((Boolean) getField(args, "xx"), is(Boolean.FALSE));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetArgsShouldSetPxForMilliseconds() {
SetArgs args = LettuceConverters.toSetArgs(Expiration.milliseconds(100), null);
assertThat(getField(args, "ex"), is(nullValue()));
assertThat((Long) getField(args, "px"), is(100L));
assertThat((Boolean) getField(args, "nx"), is(Boolean.FALSE));
assertThat((Boolean) getField(args, "xx"), is(Boolean.FALSE));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetArgsShouldSetNxForAbsent() {
SetArgs args = LettuceConverters.toSetArgs(null, SetOption.ifAbsent());
assertThat(getField(args, "ex"), is(nullValue()));
assertThat(getField(args, "px"), is(nullValue()));
assertThat((Boolean) getField(args, "nx"), is(Boolean.TRUE));
assertThat((Boolean) getField(args, "xx"), is(Boolean.FALSE));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetArgsShouldSetXxForPresent() {
SetArgs args = LettuceConverters.toSetArgs(null, SetOption.ifPresent());
assertThat(getField(args, "ex"), is(nullValue()));
assertThat(getField(args, "px"), is(nullValue()));
assertThat((Boolean) getField(args, "nx"), is(Boolean.FALSE));
assertThat((Boolean) getField(args, "xx"), is(Boolean.TRUE));
}
/**
* @see DATAREDIS-316
*/
@Test
public void toSetArgsShouldNotSetNxOrXxForUpsert() {
SetArgs args = LettuceConverters.toSetArgs(null, SetOption.upsert());
assertThat(getField(args, "ex"), is(nullValue()));
assertThat(getField(args, "px"), is(nullValue()));
assertThat((Boolean) getField(args, "nx"), is(Boolean.FALSE));
assertThat((Boolean) getField(args, "xx"), is(Boolean.FALSE));
}
}