Skip to content

Commit a69b2f2

Browse files
Introduce HGETEX command to the Spring Data Redis framework
Signed-off-by: viktoriya.kutsarova <viktoriya.kutsarova@redis.com>
1 parent 6a67e42 commit a69b2f2

24 files changed

+760
-12
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,6 +1615,11 @@ public List<String> hGetDel(String key, String... fields) {
16151615
return convertAndReturn(delegate.hGetDel(serialize(key), serializeMulti(fields)), byteListToStringList);
16161616
}
16171617

1618+
@Override
1619+
public List<String> hGetEx(String key, Expiration expiration, String... fields) {
1620+
return convertAndReturn(delegate.hGetEx(serialize(key), expiration, serializeMulti(fields)), byteListToStringList);
1621+
}
1622+
16181623
@Override
16191624
public Long incr(String key) {
16201625
return incr(serialize(key));
@@ -2593,6 +2598,11 @@ public List<byte[]> hGetDel(@NotNull byte[] key, @NotNull byte[]... fields) {
25932598
return convertAndReturn(delegate.hGetDel(key, fields), Converters.identityConverter());
25942599
}
25952600

2601+
@Override
2602+
public List<byte[]> hGetEx(@NotNull byte[] key, Expiration expiration, @NotNull byte[]... fields) {
2603+
return convertAndReturn(delegate.hGetEx(key, expiration, fields), Converters.identityConverter());
2604+
}
2605+
25962606
public @Nullable List<Long> applyExpiration(String key,
25972607
org.springframework.data.redis.core.types.Expiration expiration,
25982608
ExpirationOptions options, String... fields) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,13 @@ default List<byte[]> hGetDel(byte[] key, byte[]... fields) {
16011601
return hashCommands().hGetDel(key, fields);
16021602
}
16031603

1604+
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
1605+
@Override
1606+
@Deprecated
1607+
default List<byte[]> hGetEx(byte[] key, Expiration expiration, byte[]... fields) {
1608+
return hashCommands().hGetEx(key, expiration, fields);
1609+
}
1610+
16041611
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
16051612
@Override
16061613
@Deprecated

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,4 +1338,76 @@ default Mono<List<ByteBuffer>> hGetDel(ByteBuffer key, Collection<ByteBuffer> fi
13381338
* @see <a href="https://redis.io/commands/hgetdel">Redis Documentation: HGETDEL</a>
13391339
*/
13401340
Flux<MultiValueResponse<HGetDelCommand, ByteBuffer>> hGetDel(Publisher<HGetDelCommand> commands);
1341+
1342+
class HGetExCommand extends HashFieldsCommand {
1343+
1344+
private final Expiration expiration;
1345+
1346+
private HGetExCommand(@Nullable ByteBuffer key, List<ByteBuffer> fields, Expiration expiration) {
1347+
1348+
super(key, fields);
1349+
1350+
this.expiration = expiration;
1351+
}
1352+
1353+
/**
1354+
* Creates a new {@link HGetExCommand}.
1355+
*
1356+
* @param fields the {@code fields} names to apply expiration to
1357+
* @param expiration the {@link Expiration} to apply to the given {@literal fields}.
1358+
* @return new instance of {@link HGetExCommand}.
1359+
*/
1360+
public static HGetExCommand expire(List<ByteBuffer> fields, Expiration expiration) {
1361+
return new HGetExCommand(null, fields, expiration);
1362+
}
1363+
1364+
/**
1365+
* @param key the {@literal key} from which to expire the {@literal fields} from.
1366+
* @return new instance of {@link HashExpireCommand}.
1367+
*/
1368+
public HGetExCommand from(ByteBuffer key) {
1369+
return new HGetExCommand(key, getFields(), expiration);
1370+
}
1371+
1372+
/**
1373+
* Creates a new {@link HGetExCommand}.
1374+
*
1375+
* @param fields the {@code fields} names to apply expiration to
1376+
* @return new instance of {@link HGetExCommand}.
1377+
*/
1378+
public HGetExCommand fields(Collection<ByteBuffer> fields) {
1379+
return new HGetExCommand(getKey(), new ArrayList<>(fields), expiration);
1380+
}
1381+
1382+
public Expiration getExpiration() {
1383+
return expiration;
1384+
}
1385+
}
1386+
1387+
/**
1388+
* Get the value of one or more {@literal fields} from hash at {@literal key} and optionally set expiration time or
1389+
* time-to-live (TTL) for given {@literal fields}.
1390+
*
1391+
* @param key must not be {@literal null}.
1392+
* @param fields must not be {@literal null}.
1393+
* @return never {@literal null}.
1394+
* @see <a href="https://redis.io/commands/hgetex">Redis Documentation: HGETEX</a>
1395+
*/
1396+
default Mono<List<ByteBuffer>> hGetEx(ByteBuffer key, Expiration expiration, List<ByteBuffer> fields) {
1397+
1398+
Assert.notNull(key, "Key must not be null");
1399+
Assert.notNull(fields, "Fields must not be null");
1400+
1401+
return hGetEx(Mono.just(HGetExCommand.expire(fields, expiration).from(key))).next().map(MultiValueResponse::getOutput);
1402+
}
1403+
1404+
/**
1405+
* Get the value of one or more {@literal fields} from hash at {@literal key} and optionally set expiration time or
1406+
* time-to-live (TTL) for given {@literal fields}.
1407+
*
1408+
* @param commands must not be {@literal null}.
1409+
* @return never {@literal null}.
1410+
* @see <a href="https://redis.io/commands/hgetex">Redis Documentation: HGETEX</a>
1411+
*/
1412+
Flux<MultiValueResponse<HGetExCommand, ByteBuffer>> hGetEx(Publisher<HGetExCommand> commands);
13411413
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.jspecify.annotations.NullUnmarked;
2626
import org.springframework.data.redis.core.Cursor;
2727
import org.springframework.data.redis.core.ScanOptions;
28+
import org.springframework.data.redis.core.types.Expiration;
2829
import org.springframework.util.ObjectUtils;
2930

3031
/**
@@ -554,4 +555,17 @@ default List<Long> hExpireAt(byte @NonNull [] key, long unixTime, byte @NonNull
554555
* @see <a href="https://redis.io/commands/hgetdel">Redis Documentation: HGETDEL</a>
555556
*/
556557
List<byte[]> hGetDel(byte @NonNull [] key, byte @NonNull [] @NonNull... fields);
558+
559+
/**
560+
* Get the value of one or more {@code fields} from hash at {@code key} and optionally set expiration time or
561+
* time-to-live (TTL) for given {@code fields}.
562+
*
563+
* @param key must not be {@literal null}.
564+
* @param fields must not be {@literal null}.
565+
* @return empty {@link List} if key does not exist. {@literal null} when used in pipeline / transaction.
566+
* @see <a href="https://redis.io/commands/hgetex">Redis Documentation: HGETEX</a>
567+
*/
568+
List<byte[]> hGetEx(byte @NonNull [] key, Expiration expiration,
569+
byte @NonNull [] @NonNull... fields);
570+
557571
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2564,6 +2564,18 @@ List<Long> hpExpireAt(@NonNull String key, long unixTimeInMillis, ExpirationOpti
25642564
*/
25652565
List<String> hGetDel(@NonNull String key, @NonNull String @NonNull... fields);
25662566

2567+
/**
2568+
* Get the value of one or more {@code fields} from hash at {@code key} and optionally set expiration time or
2569+
* time-to-live (TTL) for given {@code fields}.
2570+
*
2571+
* @param key must not be {@literal null}.
2572+
* @param fields must not be {@literal null}.
2573+
* @return empty {@link List} if key does not exist. {@literal null} when used in pipeline / transaction.
2574+
* @see <a href="https://redis.io/commands/hgetex">Redis Documentation: HGETEX</a>
2575+
* @see RedisHashCommands#hGetEx(byte[], Expiration, byte[]...)
2576+
*/
2577+
List<String> hGetEx(@NonNull String key, Expiration expiration, @NonNull String @NonNull... fields);
2578+
25672579
// -------------------------------------------------------------------------
25682580
// Methods dealing with HyperLogLog
25692581
// -------------------------------------------------------------------------

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.redis.connection.jedis;
1717

18+
import org.springframework.data.redis.core.types.Expiration;
1819
import redis.clients.jedis.args.ExpiryOption;
1920
import redis.clients.jedis.params.ScanParams;
2021
import redis.clients.jedis.resps.ScanResult;
@@ -425,7 +426,19 @@ public List<byte[]> hGetDel(byte[] key, byte[]... fields) {
425426
} catch (Exception ex) {
426427
throw convertJedisAccessException(ex);
427428
}
429+
}
430+
431+
@Override
432+
public List<byte[]> hGetEx(byte[] key, Expiration expiration, byte[]... fields) {
433+
434+
Assert.notNull(key, "Key must not be null");
435+
Assert.notNull(fields, "Fields must not be null");
428436

437+
try {
438+
return connection.getCluster().hgetex(key, JedisConverters.toHGetExParams(expiration), fields);
439+
} catch (Exception ex) {
440+
throw convertJedisAccessException(ex);
441+
}
429442
}
430443

431444
@Nullable

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

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,7 @@
2222
import redis.clients.jedis.args.FlushMode;
2323
import redis.clients.jedis.args.GeoUnit;
2424
import redis.clients.jedis.args.ListPosition;
25-
import redis.clients.jedis.params.GeoRadiusParam;
26-
import redis.clients.jedis.params.GeoSearchParam;
27-
import redis.clients.jedis.params.GetExParams;
28-
import redis.clients.jedis.params.ScanParams;
29-
import redis.clients.jedis.params.SetParams;
30-
import redis.clients.jedis.params.SortingParams;
31-
import redis.clients.jedis.params.ZAddParams;
25+
import redis.clients.jedis.params.*;
3226
import redis.clients.jedis.resps.GeoRadiusResponse;
3327
import redis.clients.jedis.util.SafeEncoder;
3428

@@ -398,6 +392,40 @@ static GetExParams toGetExParams(Expiration expiration, GetExParams params) {
398392
: params.ex(expiration.getConverted(TimeUnit.SECONDS));
399393
}
400394

395+
/**
396+
* Converts a given {@link Expiration} to the according {@code HGETEX} command argument depending on
397+
* {@link Expiration#isUnixTimestamp()}.
398+
* <dl>
399+
* <dt>{@link TimeUnit#MILLISECONDS}</dt>
400+
* <dd>{@code PX|PXAT}</dd>
401+
* <dt>{@link TimeUnit#SECONDS}</dt>
402+
* <dd>{@code EX|EXAT}</dd>
403+
* </dl>
404+
*
405+
* @param expiration must not be {@literal null}.
406+
* @since 4.0
407+
*/
408+
static HGetExParams toHGetExParams(Expiration expiration) {
409+
return toHGetExParams(expiration, new HGetExParams());
410+
}
411+
412+
static HGetExParams toHGetExParams(Expiration expiration, HGetExParams params) {
413+
414+
if (expiration.isPersistent()) {
415+
return params.persist();
416+
}
417+
418+
if (expiration.getTimeUnit() == TimeUnit.MILLISECONDS) {
419+
if (expiration.isUnixTimestamp()) {
420+
return params.pxAt(expiration.getExpirationTime());
421+
}
422+
return params.px(expiration.getExpirationTime());
423+
}
424+
425+
return expiration.isUnixTimestamp() ? params.exAt(expiration.getConverted(TimeUnit.SECONDS))
426+
: params.ex(expiration.getConverted(TimeUnit.SECONDS));
427+
}
428+
401429
/**
402430
* Converts a given {@link SetOption} to the according {@code SET} command argument.<br />
403431
* <dl>

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.redis.connection.jedis;
1717

18+
import org.springframework.data.redis.core.types.Expiration;
1819
import redis.clients.jedis.Jedis;
1920
import redis.clients.jedis.args.ExpiryOption;
2021
import redis.clients.jedis.commands.PipelineBinaryCommands;
@@ -341,6 +342,15 @@ public List<byte[]> hGetDel(byte @NonNull [] key, byte @NonNull [] @NonNull... f
341342
return connection.invoke().just(Jedis::hgetdel, PipelineBinaryCommands::hgetdel, key, fields);
342343
}
343344

345+
@Override
346+
public List<byte[]> hGetEx(byte @NonNull [] key, Expiration expiration, byte @NonNull [] @NonNull... fields) {
347+
348+
Assert.notNull(key, "Key must not be null");
349+
Assert.notNull(fields, "Fields must not be null");
350+
351+
return connection.invoke().just(Jedis::hgetex, PipelineBinaryCommands::hgetex, key, JedisConverters.toHGetExParams(expiration), fields);
352+
}
353+
344354
@Nullable
345355
@Override
346356
public Long hStrLen(byte[] key, byte[] field) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1244,8 +1244,9 @@ static class TypeHints {
12441244
COMMAND_OUTPUT_TYPE_MAPPING.put(ZREVRANGE, ValueListOutput.class);
12451245
COMMAND_OUTPUT_TYPE_MAPPING.put(ZREVRANGEBYSCORE, ValueListOutput.class);
12461246
COMMAND_OUTPUT_TYPE_MAPPING.put(HGETDEL, ValueListOutput.class);
1247+
COMMAND_OUTPUT_TYPE_MAPPING.put(HGETEX, ValueListOutput.class);
12471248

1248-
// BOOLEAN
1249+
// BOOLEAN
12491250
COMMAND_OUTPUT_TYPE_MAPPING.put(EXISTS, BooleanOutput.class);
12501251
COMMAND_OUTPUT_TYPE_MAPPING.put(EXPIRE, BooleanOutput.class);
12511252
COMMAND_OUTPUT_TYPE_MAPPING.put(EXPIREAT, BooleanOutput.class);

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import io.lettuce.core.cluster.models.partitions.RedisClusterNode.NodeFlag;
2424

2525
import java.nio.charset.StandardCharsets;
26+
import java.time.Duration;
27+
import java.time.Instant;
2628
import java.util.*;
2729
import java.util.concurrent.TimeUnit;
2830
import java.util.stream.Collectors;
@@ -620,6 +622,35 @@ static GetExArgs toGetExArgs(@Nullable Expiration expiration) {
620622
: args.ex(expiration.getConverted(TimeUnit.SECONDS));
621623
}
622624

625+
/**
626+
* Convert {@link Expiration} to {@link HGetExArgs}.
627+
*
628+
* @param expiration can be {@literal null}.
629+
* @since 4.0
630+
*/
631+
static HGetExArgs toHGetExArgs(@Nullable Expiration expiration) {
632+
633+
HGetExArgs args = new HGetExArgs();
634+
635+
if (expiration == null) {
636+
return args;
637+
}
638+
639+
if (expiration.isPersistent()) {
640+
return args.persist();
641+
}
642+
643+
if (expiration.getTimeUnit() == TimeUnit.MILLISECONDS) {
644+
if (expiration.isUnixTimestamp()) {
645+
return args.pxAt(Instant.ofEpochSecond(expiration.getExpirationTime()));
646+
}
647+
return args.px(Duration.ofMillis(expiration.getExpirationTime()));
648+
}
649+
650+
return expiration.isUnixTimestamp() ? args.exAt(Instant.ofEpochSecond(expiration.getConverted(TimeUnit.SECONDS)))
651+
: args.ex(Duration.ofSeconds(expiration.getConverted(TimeUnit.SECONDS)));
652+
}
653+
623654
@SuppressWarnings("NullAway")
624655
static Converter<List<byte[]>, Long> toTimeConverter(TimeUnit timeUnit) {
625656

0 commit comments

Comments
 (0)