Skip to content

Commit 4b2ccbe

Browse files
christophstroblThomas Darimont
authored and
Thomas Darimont
committed
DATAREDIS-271 - Add support for 'pSetEx'.
Support for 'pSetEx' has been added for 'jedis' & 'srp'. For 'lettuce' the command is emulated using 'eval'. There is no support for 'pSetEx' when using 'jredis'. 'DefaultValueOperations.set' used with 'TimeUnit.MILLISECONDS' will fallback to 'setEx' in case 'pSetEx' is not supported by the driver in use. Original pull request: #46.
1 parent a3ac8d0 commit 4b2ccbe

18 files changed

+244
-11
lines changed

docs/src/reference/docbook/appendix/appendix-command-reference.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
<row><entry><code>PEXIPRE</code></entry><entry>X</entry></row>
9595
<row><entry><code>PEXPIREAT</code></entry><entry>X</entry></row>
9696
<row><entry><code>PING</code></entry><entry>X</entry></row>
97-
<row><entry><code>PSETEX</code></entry><entry>-</entry></row>
97+
<row><entry><code>PSETEX</code></entry><entry>X</entry></row>
9898
<row><entry><code>PSUBSCRIBE</code></entry><entry>X</entry></row>
9999
<row><entry><code>PTTL</code></entry><entry>X</entry></row>
100100
<row><entry><code>PUBLISH</code></entry><entry>X</entry></row>

src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,15 @@ public void setEx(byte[] key, long seconds, byte[] value) {
735735
delegate.setEx(key, seconds, value);
736736
}
737737

738+
/*
739+
* (non-Javadoc)
740+
* @see org.springframework.data.redis.connection.RedisStringCommands#pSetEx(byte[], long, byte[])
741+
*/
742+
@Override
743+
public void pSetEx(byte[] key, long milliseconds, byte[] value) {
744+
delegate.pSetEx(key, milliseconds, value);
745+
}
746+
738747
public Boolean setNX(byte[] key, byte[] value) {
739748
Boolean result = delegate.setNX(key, value);
740749
if (isFutureConversion()) {
@@ -1695,6 +1704,15 @@ public void setEx(String key, long seconds, String value) {
16951704
delegate.setEx(serialize(key), seconds, serialize(value));
16961705
}
16971706

1707+
/*
1708+
* (non-Javadoc)
1709+
* @see org.springframework.data.redis.connection.StringRedisConnection#pSetEx(java.lang.String, long, java.lang.String)
1710+
*/
1711+
@Override
1712+
public void pSetEx(String key, long seconds, String value) {
1713+
pSetEx(serialize(key), seconds, serialize(value));
1714+
}
1715+
16981716
public Boolean setNX(String key, String value) {
16991717
Boolean result = delegate.setNX(serialize(key), serialize(value));
17001718
if (isFutureConversion()) {

src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ public enum BitOperation {
8787
*/
8888
void setEx(byte[] key, long seconds, byte[] value);
8989

90+
/**
91+
* Set the {@code value} and expiration in {@code milliseconds} for {@code key}.
92+
*
93+
* @see http://redis.io/commands/psetex
94+
* @param key
95+
* @param milliseconds
96+
* @param value
97+
* @since 1.3
98+
*/
99+
void pSetEx(byte[] key, long milliseconds, byte[] value);
100+
90101
/**
91102
* Set multiple keys to multiple values using key-value pairs provided in {@code tuple}.
92103
*

src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2013 the original author or authors.
2+
* Copyright 2011-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
* Uses a {@link RedisSerializer} underneath to perform the conversion.
3030
*
3131
* @author Costin Leau
32+
* @author Christoph Strobl
3233
* @see RedisCallback
3334
* @see RedisSerializer
3435
* @see StringRedisTemplate
@@ -93,6 +94,17 @@ public interface StringTuple extends Tuple {
9394

9495
void setEx(String key, long seconds, String value);
9596

97+
/**
98+
* Set the {@code value} and expiration in {@code milliseconds} for {@code key}.
99+
*
100+
* @see http://redis.io/commands/psetex
101+
* @param key
102+
* @param seconds
103+
* @param value
104+
* @since 1.3
105+
*/
106+
void pSetEx(String key, long milliseconds, String value);
107+
96108
void mSetString(Map<String, String> tuple);
97109

98110
Boolean mSetNXString(Map<String, String> tuple);

src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,10 @@ private List<Object> convertPipelineResults() {
331331
return results;
332332
}
333333

334+
private void doPipelined(Response<?> response) {
335+
pipeline(new JedisStatusResult(response));
336+
}
337+
334338
private void pipeline(FutureResult<Response<?>> result) {
335339
if (isQueueing()) {
336340
transaction(result);
@@ -339,6 +343,10 @@ private void pipeline(FutureResult<Response<?>> result) {
339343
}
340344
}
341345

346+
private void doQueued(Response<?> response) {
347+
transaction(new JedisStatusResult(response));
348+
}
349+
342350
private void transaction(FutureResult<Response<?>> result) {
343351
txResults.add(result);
344352
}
@@ -1141,6 +1149,28 @@ public void setEx(byte[] key, long time, byte[] value) {
11411149
}
11421150
}
11431151

1152+
/*
1153+
* (non-Javadoc)
1154+
* @see org.springframework.data.redis.connection.RedisStringCommands#pSetEx(byte[], long, byte[])
1155+
*/
1156+
@Override
1157+
public void pSetEx(byte[] key, long milliseconds, byte[] value) {
1158+
1159+
try {
1160+
if (isPipelined()) {
1161+
doPipelined(pipeline.psetex(key, (int) milliseconds, value));
1162+
return;
1163+
}
1164+
if (isQueueing()) {
1165+
doQueued(transaction.psetex(key, (int) milliseconds, value));
1166+
return;
1167+
}
1168+
jedis.psetex(key, (int) milliseconds, value);
1169+
} catch (Exception ex) {
1170+
throw convertJedisAccessException(ex);
1171+
}
1172+
}
1173+
11441174
public Boolean setNX(byte[] key, byte[] value) {
11451175
try {
11461176
if (isPipelined()) {

src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,15 @@ public void setEx(byte[] key, long seconds, byte[] value) {
495495
throw new UnsupportedOperationException();
496496
}
497497

498+
/*
499+
* (non-Javadoc)
500+
* @see org.springframework.data.redis.connection.RedisStringCommands#pSetEx(byte[], long, byte[])
501+
*/
502+
@Override
503+
public void pSetEx(byte[] key, long milliseconds, byte[] value) {
504+
throw new UnsupportedOperationException();
505+
}
506+
498507
public Boolean setNX(byte[] key, byte[] value) {
499508
try {
500509
return jredis.setnx(key, value);

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import com.lambdaworks.redis.RedisAsyncConnection;
5757
import com.lambdaworks.redis.RedisClient;
5858
import com.lambdaworks.redis.RedisException;
59+
import com.lambdaworks.redis.ScriptOutputType;
5960
import com.lambdaworks.redis.SortArgs;
6061
import com.lambdaworks.redis.ZStoreArgs;
6162
import com.lambdaworks.redis.codec.RedisCodec;
@@ -1197,6 +1198,49 @@ public void setEx(byte[] key, long time, byte[] value) {
11971198
}
11981199
}
11991200

1201+
/**
1202+
* {@code pSetEx} is not directly supported and therefore emulated via {@literal lua script}.
1203+
*
1204+
* @since 1.3
1205+
* @see org.springframework.data.redis.connection.RedisStringCommands#pSetEx(byte[], long, byte[])
1206+
*/
1207+
@Override
1208+
public void pSetEx(byte[] key, long milliseconds, byte[] value) {
1209+
1210+
byte[] script = createRedisScriptForPSetEx(key, milliseconds, value);
1211+
byte[][] emptyArgs = new byte[0][0];
1212+
1213+
try {
1214+
if (isPipelined()) {
1215+
pipeline(new LettuceStatusResult(getAsyncConnection().eval(script, ScriptOutputType.STATUS, emptyArgs,
1216+
emptyArgs)));
1217+
return;
1218+
}
1219+
if (isQueueing()) {
1220+
transaction(new LettuceTxStatusResult(getConnection().eval(script, ScriptOutputType.STATUS, emptyArgs,
1221+
emptyArgs)));
1222+
return;
1223+
}
1224+
this.eval(script, ReturnType.STATUS, 0);
1225+
} catch (Exception ex) {
1226+
throw convertLettuceAccessException(ex);
1227+
}
1228+
}
1229+
1230+
private byte[] createRedisScriptForPSetEx(byte[] key, long milliseconds, byte[] value) {
1231+
1232+
StringBuilder sb = new StringBuilder("return redis.call('PSETEX'");
1233+
sb.append(",'");
1234+
sb.append(new String(key));
1235+
sb.append("',");
1236+
sb.append(milliseconds);
1237+
sb.append(",'");
1238+
sb.append(new String(value));
1239+
sb.append("')");
1240+
1241+
return sb.toString().getBytes();
1242+
}
1243+
12001244
public Boolean setNX(byte[] key, byte[] value) {
12011245
try {
12021246
if (isPipelined()) {

src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,24 @@ public void setEx(byte[] key, long time, byte[] value) {
900900
}
901901
}
902902

903+
/*
904+
* (non-Javadoc)
905+
* @see org.springframework.data.redis.connection.RedisStringCommands#pSetEx(byte[], long, byte[])
906+
*/
907+
@Override
908+
public void pSetEx(byte[] key, long milliseconds, byte[] value) {
909+
910+
try {
911+
if (isPipelined()) {
912+
doPipelined(pipeline.psetex(key, milliseconds, value));
913+
return;
914+
}
915+
client.psetex(key, milliseconds, value);
916+
} catch (Exception ex) {
917+
throw convertSrpAccessException(ex);
918+
}
919+
}
920+
903921
public Boolean setNX(byte[] key, byte[] value) {
904922
try {
905923
if (isPipelined()) {
@@ -2113,6 +2131,11 @@ private void checkSubscription() {
21132131
}
21142132
}
21152133

2134+
@SuppressWarnings("rawtypes")
2135+
private void doPipelined(ListenableFuture<Reply> listenableFuture) {
2136+
pipeline(new SrpStatusResult(listenableFuture));
2137+
}
2138+
21162139
// processing method that adds a listener to the future in order to track down the results and close the pipeline
21172140
private void pipeline(FutureResult<?> future) {
21182141
if (isQueueing()) {

src/main/java/org/springframework/data/redis/core/DefaultValueOperations.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2013 the original author or authors.
2+
* Copyright 2011-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
*
3131
* @author Costin Leau
3232
* @author Jennifer Hickey
33+
* @author Christoph Strobl
3334
*/
3435
class DefaultValueOperations<K, V> extends AbstractOperations<K, V> implements ValueOperations<K, V> {
3536

@@ -173,17 +174,37 @@ protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
173174
}, true);
174175
}
175176

176-
public void set(K key, V value, long timeout, TimeUnit unit) {
177+
public void set(K key, V value, final long timeout, final TimeUnit unit) {
177178
final byte[] rawKey = rawKey(key);
178179
final byte[] rawValue = rawValue(value);
179-
final long rawTimeout = TimeoutUtils.toSeconds(timeout, unit);
180180

181181
execute(new RedisCallback<Object>() {
182182

183183
public Object doInRedis(RedisConnection connection) throws DataAccessException {
184-
connection.setEx(rawKey, rawTimeout, rawValue);
184+
185+
potentiallyUsePsetEx(connection);
185186
return null;
186187
}
188+
189+
public void potentiallyUsePsetEx(RedisConnection connection) {
190+
191+
if (!TimeUnit.MILLISECONDS.equals(unit) || !failsafeInvokePsetEx(connection)) {
192+
connection.setEx(rawKey, TimeoutUtils.toSeconds(timeout, unit), rawValue);
193+
}
194+
}
195+
196+
private boolean failsafeInvokePsetEx(RedisConnection connection) {
197+
198+
boolean failed = false;
199+
try {
200+
connection.pSetEx(rawKey, timeout, rawValue);
201+
} catch (UnsupportedOperationException e) {
202+
// in case the connection does not support pSetEx return false to allow fallback to other operation.
203+
failed = true;
204+
}
205+
return !failed;
206+
}
207+
187208
}, true);
188209
}
189210

src/main/java/org/springframework/data/redis/core/ValueOperations.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2013 the original author or authors.
2+
* Copyright 2011-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,11 +24,21 @@
2424
* Redis operations for simple (or in Redis terminology 'string') values.
2525
*
2626
* @author Costin Leau
27+
* @author Christoph Strobl
2728
*/
2829
public interface ValueOperations<K, V> {
2930

3031
void set(K key, V value);
3132

33+
/**
34+
* Set {@code key} to hold the string {@code value} until {@code timeout}.
35+
*
36+
* @param key
37+
* @param value
38+
* @param timeout
39+
* @param units
40+
* @see http://redis.io/commands/set
41+
*/
3242
void set(K key, V value, long timeout, TimeUnit unit);
3343

3444
Boolean setIfAbsent(K key, V value);

src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
* @author Costin Leau
6767
* @author Jennifer Hickey
6868
* @author Christoph Strobl
69+
* @author Thomas Darimont
6970
*/
7071
@ProfileValueSourceConfiguration(RedisTestProfileValueSource.class)
7172
public abstract class AbstractConnectionIntegrationTests {
@@ -359,6 +360,20 @@ public void testSetEx() throws Exception {
359360
assertTrue(waitFor(new KeyExpired("expy"), 2500l));
360361
}
361362

363+
/**
364+
* @see DATAREDIS-271
365+
*/
366+
@Test
367+
@IfProfileValue(name = "runLongTests", value = "true")
368+
public void testPsetEx() throws Exception {
369+
370+
connection.pSetEx("expy", 500L, "yep");
371+
actual.add(connection.get("expy"));
372+
373+
verifyResults(Arrays.asList(new Object[] { "yep" }));
374+
assertTrue(waitFor(new KeyExpired("expy"), 2500L));
375+
}
376+
362377
@Test
363378
@IfProfileValue(name = "runLongTests", value = "true")
364379
public void testBRPopTimeout() throws Exception {

src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013 the original author or authors.
2+
* Copyright 2013-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@
4545
* Unit test of {@link DefaultStringRedisConnection}
4646
*
4747
* @author Jennifer Hickey
48+
* @auhtor Christoph Strobl
4849
*/
4950
public class DefaultStringRedisConnectionTests {
5051

@@ -907,6 +908,16 @@ public void testSetNX() {
907908
verifyResults(Arrays.asList(new Object[] { true }));
908909
}
909910

911+
/**
912+
* @see DATAREDIS-271
913+
*/
914+
@Test
915+
public void testPSetExShouldDelegateCallToNativeConnection() {
916+
917+
connection.pSetEx(fooBytes, 10L, barBytes);
918+
verify(nativeConnection, times(1)).pSetEx(eq(fooBytes), eq(10L), eq(barBytes));
919+
}
920+
910921
@Test
911922
public void testSInterBytes() {
912923
doReturn(bytesSet).when(nativeConnection).sInter(fooBytes, barBytes);

0 commit comments

Comments
 (0)