Add GETEX options EXAT, PXAT

Original Pull Request: #2086
This commit is contained in:
Christoph Strobl
2021-06-28 10:23:35 +02:00
parent a53193946f
commit f0b22ad76f
8 changed files with 222 additions and 24 deletions

View File

@@ -17,6 +17,7 @@ package org.springframework.data.redis.connection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.core.types.Expiration;
@@ -58,10 +59,13 @@ public interface RedisStringCommands {
/**
* Return the value at {@code key} and expire the key by applying {@link Expiration}.
* <p />
* Use {@link Expiration#seconds(long)} for {@code EX}. <br />
* Use {@link Expiration#milliseconds(long)} for {@code PX}. <br />
* Use {@link Expiration#unixTimestamp(long, TimeUnit)} for {@code EXAT | PXAT}. <br />
*
* @param key must not be {@literal null}.
* @param expiration must not be {@literal null}.
* @param unit must not be {@literal null}.
* @return {@literal null} when key does not exist or used in pipeline / transaction.
* @see <a href="https://redis.io/commands/getex">Redis Documentation: GETEX</a>
* @since 2.6

View File

@@ -443,12 +443,13 @@ public abstract class JedisConverters extends Converters {
}
/**
* Converts a given {@link Expiration} to the according {@code GETEX} command argument.
* Converts a given {@link Expiration} to the according {@code GETEX} command argument depending on
* {@link Expiration#isUnixTimestamp()}.
* <dl>
* <dt>{@link TimeUnit#MILLISECONDS}</dt>
* <dd>{@code PX}</dd>
* <dd>{@code PX|PXAT}</dd>
* <dt>{@link TimeUnit#SECONDS}</dt>
* <dd>{@code EX}</dd>
* <dd>{@code EX|EXAT}</dd>
* </dl>
*
* @param expiration must not be {@literal null}.
@@ -464,10 +465,14 @@ public abstract class JedisConverters extends Converters {
}
if (expiration.getTimeUnit() == TimeUnit.MILLISECONDS) {
if (expiration.isUnixTimestamp()) {
return params.pxAt(expiration.getExpirationTime());
}
return params.px(expiration.getExpirationTime());
}
return params.ex((int) expiration.getExpirationTime());
return expiration.isUnixTimestamp() ? params.exAt(expiration.getConverted(TimeUnit.SECONDS))
: params.ex(expiration.getConverted(TimeUnit.SECONDS));
}
/**

View File

@@ -728,32 +728,31 @@ public abstract class LettuceConverters extends Converters {
/**
* Convert {@link Expiration} to {@link GetExArgs}.
*
* @param expiration
* @param expiration can be {@literal null}.
* @return
* @since 2.6
*/
static GetExArgs toGetExArgs(Expiration expiration) {
static GetExArgs toGetExArgs(@Nullable Expiration expiration) {
GetExArgs args = new GetExArgs();
if (expiration != null) {
if (expiration.isPersistent()) {
args.persist();
} else if (!expiration.isPersistent()) {
switch (expiration.getTimeUnit()) {
case SECONDS:
args.ex(expiration.getExpirationTime());
break;
default:
args.px(expiration.getConverted(TimeUnit.MILLISECONDS));
break;
}
}
if (expiration == null) {
return args;
}
return args;
if(expiration.isPersistent()) {
return args.persist();
}
if (expiration.getTimeUnit() == TimeUnit.MILLISECONDS) {
if (expiration.isUnixTimestamp()) {
return args.pxAt(expiration.getExpirationTime());
}
return args.px(expiration.getExpirationTime());
}
return expiration.isUnixTimestamp() ? args.exAt(expiration.getConverted(TimeUnit.SECONDS))
: args.ex(expiration.getConverted(TimeUnit.SECONDS));
}
static Converter<List<byte[]>, Long> toTimeConverter(TimeUnit timeUnit) {

View File

@@ -115,6 +115,17 @@ public class Expiration {
return new Expiration(expirationTime, TimeUnit.MILLISECONDS);
}
/**
* Creates new {@link Expiration} with the given {@literal unix timestamp} and {@link TimeUnit}.
*
* @param unixTimestamp the unix timestamp at which the key will expire.
* @param timeUnit must not be {@literal null}.
* @return new instance of {@link Expiration}.
*/
public static Expiration unixTimestamp(long unixTimestamp, TimeUnit timeUnit) {
return new ExpireAt(unixTimestamp, timeUnit);
}
/**
* Obtain an {@link Expiration} that indicates to keep the existing one. Eg. when sending a {@code SET} command.
* <p />
@@ -197,6 +208,14 @@ public class Expiration {
return false;
}
/**
* @return {@literal true} if {@link Expiration} is set to a specified Unix time at which the key will expire.
* @since 2.6
*/
public boolean isUnixTimestamp() {
return false;
}
/**
* @author Christoph Strobl
* @since 2.4
@@ -214,4 +233,19 @@ public class Expiration {
return true;
}
}
/**
* @author Christoph Strobl
* @since 2.6
*/
private static class ExpireAt extends Expiration {
private ExpireAt(long expirationTime, @Nullable TimeUnit timeUnit) {
super(expirationTime, timeUnit);
}
public boolean isUnixTimestamp() {
return true;
}
}
}

View File

@@ -1138,6 +1138,7 @@ public abstract class AbstractConnectionIntegrationTests {
@Test // GH-2050
@EnabledOnCommand("GETEX")
void testGetEx() {
actual.add(connection.set("testGS", "1"));
actual.add(connection.getEx("testGS", Expiration.seconds(10)));
actual.add(connection.ttl("testGS"));
@@ -1150,6 +1151,7 @@ public abstract class AbstractConnectionIntegrationTests {
@Test // GH-2050
@EnabledOnCommand("GETDEL")
void testGetDel() {
actual.add(connection.set("testGS", "1"));
actual.add(connection.getDel("testGS"));
actual.add(connection.exists("testGS"));
@@ -1159,6 +1161,7 @@ public abstract class AbstractConnectionIntegrationTests {
@Test
void testGetSet() {
actual.add(connection.set("testGS", "1"));
actual.add(connection.getSet("testGS", "2"));
actual.add(connection.get("testGS"));
@@ -1167,6 +1170,7 @@ public abstract class AbstractConnectionIntegrationTests {
@Test
void testMSet() {
Map<String, String> vals = new HashMap<>();
vals.put("color", "orange");
vals.put("size", "1");
@@ -1178,6 +1182,7 @@ public abstract class AbstractConnectionIntegrationTests {
@Test
void testMSetNx() {
Map<String, String> vals = new HashMap<>();
vals.put("height", "5");
vals.put("width", "1");
@@ -1188,6 +1193,7 @@ public abstract class AbstractConnectionIntegrationTests {
@Test
void testMSetNxFailure() {
actual.add(connection.set("height", "2"));
Map<String, String> vals = new HashMap<>();
vals.put("height", "5");
@@ -1199,6 +1205,7 @@ public abstract class AbstractConnectionIntegrationTests {
@Test
void testSetNx() {
actual.add(connection.setNX("notaround", "54"));
actual.add(connection.get("notaround"));
actual.add(connection.setNX("notaround", "55"));

View File

@@ -18,6 +18,7 @@ package org.springframework.data.redis.connection.jedis;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.params.GetExParams;
import redis.clients.jedis.params.SetParams;
import java.util.Arrays;
@@ -26,6 +27,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
@@ -226,6 +228,54 @@ class JedisConvertersUnitTests {
assertThat(toString(JedisConverters.toSetCommandNxXxArgument(SetOption.upsert()))).isEqualTo("");
}
@Test // GH-2050
void convertsExpirationToGetExEX() {
assertThat(JedisConverters.toGetExParams(Expiration.seconds(10)))
.extracting(GetExParams::toString)
.isEqualTo(new GetExParams().ex(10).toString());
}
@Test // GH-2050
void convertsExpirationWithTimeUnitToGetExEX() {
assertThat(JedisConverters.toGetExParams(Expiration.from(1, TimeUnit.MINUTES)))
.extracting(GetExParams::toString)
.isEqualTo(new GetExParams().ex(60).toString());
}
@Test // GH-2050
void convertsExpirationToGetExPEX() {
assertThat(JedisConverters.toGetExParams(Expiration.milliseconds(10)))
.extracting(GetExParams::toString)
.isEqualTo(new GetExParams().px(10).toString());
}
@Test // GH-2050
void convertsExpirationToGetExEXAT() {
assertThat(JedisConverters.toGetExParams(Expiration.unixTimestamp(10, TimeUnit.SECONDS)))
.extracting(GetExParams::toString)
.isEqualTo(new GetExParams().exAt(10).toString());
}
@Test // GH-2050
void convertsExpirationWithTimeUnitToGetExEXAT() {
assertThat(JedisConverters.toGetExParams(Expiration.unixTimestamp(1, TimeUnit.MINUTES)))
.extracting(GetExParams::toString)
.isEqualTo(new GetExParams().exAt(60).toString());
}
@Test // GH-2050
void convertsExpirationToGetExPXAT() {
assertThat(JedisConverters.toGetExParams(Expiration.unixTimestamp(10, TimeUnit.MILLISECONDS)))
.extracting(GetExParams::toString)
.isEqualTo(new GetExParams().pxAt(10).toString());
}
private void verifyRedisServerInfo(RedisServer server, Map<String, String> values) {
for (Map.Entry<String, String> entry : values.entrySet()) {

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2021 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
*
* https://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.connection.lettuce;
import io.lettuce.core.CompositeArgument;
import io.lettuce.core.codec.StringCodec;
import io.lettuce.core.protocol.CommandArgs;
import org.assertj.core.api.Assertions;
/**
* @author Christoph Strobl
*/
public class LettuceCommandArgsComparator {
static void argsEqual(CompositeArgument args1, CompositeArgument args2) {
CommandArgs<String, String> stringStringCommandArgs1 = new CommandArgs<>(StringCodec.UTF8);
args1.build(stringStringCommandArgs1);
CommandArgs<String, String> stringStringCommandArgs2 = new CommandArgs<>(StringCodec.UTF8);
args2.build(stringStringCommandArgs2);
Assertions.assertThat(stringStringCommandArgs1.toCommandString())
.isEqualTo(stringStringCommandArgs2.toCommandString());
}
static LettuceCompositeArgumentAssert assertThatCommandArgument(CompositeArgument command) {
return new LettuceCompositeArgumentAssert() {
@Override
public LettuceCompositeArgumentAssert isEqualTo(CompositeArgument expected) {
argsEqual(command, expected);
return this;
}
};
}
interface LettuceCompositeArgumentAssert {
LettuceCompositeArgumentAssert isEqualTo(CompositeArgument command);
}
}

View File

@@ -17,8 +17,10 @@ package org.springframework.data.redis.connection.lettuce;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import static org.springframework.data.redis.connection.lettuce.LettuceCommandArgsComparator.*;
import static org.springframework.test.util.ReflectionTestUtils.*;
import io.lettuce.core.GetExArgs;
import io.lettuce.core.Limit;
import io.lettuce.core.RedisURI;
import io.lettuce.core.SetArgs;
@@ -29,9 +31,9 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisClusterNode.Flag;
import org.springframework.data.redis.connection.RedisClusterNode.LinkState;
@@ -193,4 +195,45 @@ class LettuceConvertersUnitTests {
assertThat(limit.isLimited()).isTrue();
assertThat(limit.getCount()).isEqualTo(5L);
}
@Test // GH-2050
void convertsExpirationToGetExEX() {
assertThatCommandArgument(LettuceConverters.toGetExArgs(Expiration.seconds(10))).isEqualTo(new GetExArgs().ex(10));
}
@Test // GH-2050
void convertsExpirationWithTimeUnitToGetExEX() {
assertThatCommandArgument(LettuceConverters.toGetExArgs(Expiration.from(1, TimeUnit.MINUTES)))
.isEqualTo(new GetExArgs().ex(60));
}
@Test // GH-2050
void convertsExpirationToGetExPEX() {
assertThatCommandArgument(LettuceConverters.toGetExArgs(Expiration.milliseconds(10)))
.isEqualTo(new GetExArgs().px(10));
}
@Test // GH-2050
void convertsExpirationToGetExEXAT() {
assertThatCommandArgument(LettuceConverters.toGetExArgs(Expiration.unixTimestamp(10, TimeUnit.SECONDS)))
.isEqualTo(new GetExArgs().exAt(10));
}
@Test // GH-2050
void convertsExpirationWithTimeUnitToGetExEXAT() {
assertThatCommandArgument(LettuceConverters.toGetExArgs(Expiration.unixTimestamp(1, TimeUnit.MINUTES)))
.isEqualTo(new GetExArgs().exAt(60));
}
@Test // GH-2050
void convertsExpirationToGetExPXAT() {
assertThatCommandArgument(LettuceConverters.toGetExArgs(Expiration.unixTimestamp(10, TimeUnit.MILLISECONDS)))
.isEqualTo(new GetExArgs().pxAt(10));
}
}