diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java index fd1ad55ebd..08159e25f2 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java @@ -1429,6 +1429,41 @@ public RedisFuture hset(K key, Map map) { return dispatch(commandBuilder.hset(key, map)); } + @Override + public RedisFuture hsetex(K key, Map map) { + return dispatch(commandBuilder.hsetex(key, map)); + } + + @Override + public RedisFuture hsetex(K key, HSetExArgs hSetExArgs, Map map) { + return dispatch(commandBuilder.hsetex(key, hSetExArgs, map)); + } + + @Override + public RedisFuture>> hgetex(K key, K... fields) { + return dispatch(commandBuilder.hgetex(key, fields)); + } + + @Override + public RedisFuture>> hgetex(K key, HGetExArgs hGetExArgs, K... fields) { + return dispatch(commandBuilder.hgetex(key, hGetExArgs, fields)); + } + + @Override + public RedisFuture hgetex(KeyValueStreamingChannel channel, K key, HGetExArgs hGetExArgs, K... fields) { + return dispatch(commandBuilder.hgetex(channel, key, hGetExArgs, fields)); + } + + @Override + public RedisFuture>> hgetdel(K key, K... fields) { + return dispatch(commandBuilder.hgetdel(key, fields)); + } + + @Override + public RedisFuture hgetdel(KeyValueStreamingChannel channel, K key, K... fields) { + return dispatch(commandBuilder.hgetdel(channel, key, fields)); + } + @Override public RedisFuture hsetnx(K key, K field, V value) { return dispatch(commandBuilder.hsetnx(key, field, value)); diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java index e1651e5623..a920d3a2d0 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java @@ -1494,6 +1494,41 @@ public Mono hset(K key, Map map) { return createMono(() -> commandBuilder.hset(key, map)); } + @Override + public Mono hsetex(K key, Map map) { + return createMono(() -> commandBuilder.hsetex(key, map)); + } + + @Override + public Mono hsetex(K key, HSetExArgs hSetExArgs, Map map) { + return createMono(() -> commandBuilder.hsetex(key, hSetExArgs, map)); + } + + @Override + public Flux> hgetex(K key, K... fields) { + return createDissolvingFlux(() -> commandBuilder.hgetex(key, fields)); + } + + @Override + public Flux> hgetex(K key, HGetExArgs hGetExArgs, K... fields) { + return createDissolvingFlux(() -> commandBuilder.hgetex(key, hGetExArgs, fields)); + } + + @Override + public Mono hgetex(KeyValueStreamingChannel channel, K key, HGetExArgs hGetExArgs, K... fields) { + return createMono(() -> commandBuilder.hgetex(channel, key, hGetExArgs, fields)); + } + + @Override + public Flux> hgetdel(K key, K... fields) { + return createDissolvingFlux(() -> commandBuilder.hgetdel(key, fields)); + } + + @Override + public Mono hgetdel(KeyValueStreamingChannel channel, K key, K... fields) { + return createMono(() -> commandBuilder.hgetdel(channel, key, fields)); + } + @Override public Mono hsetnx(K key, K field, V value) { return createMono(() -> commandBuilder.hsetnx(key, field, value)); diff --git a/src/main/java/io/lettuce/core/HGetExArgs.java b/src/main/java/io/lettuce/core/HGetExArgs.java new file mode 100644 index 0000000000..8037ff4d3f --- /dev/null +++ b/src/main/java/io/lettuce/core/HGetExArgs.java @@ -0,0 +1,196 @@ +package io.lettuce.core; + +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandType; + +import java.time.Duration; +import java.time.Instant; +import java.util.Date; + +/** + * Argument list builder for the Redis HGETEX command starting from Redis 8.0. + * Static import the methods from {@link Builder} and chain the method calls: {@code ex(10).nx()}. + *

+ * {@link HGetExArgs} is a mutable object and instances should be used only once to avoid shared mutable state. + * + * @author Ivo Gaydajiev + * @since 6.6 + */ +public class HGetExArgs implements CompositeArgument { + + private Long ex; + + private Long exAt; + + private Long px; + + private Long pxAt; + + private boolean persist = false; + + /** + * Builder entry points for {@link HGetExArgs}. + */ + public static class Builder { + + /** + * Utility constructor. + */ + private Builder() { + } + + /** + * Creates new {@link HGetExArgs} and enable {@literal EX}. + * + * @param timeout expire time in seconds. + * @return new {@link HGetExArgs} with {@literal EX} enabled. + * @see HGetExArgs#ex(long) + * @since 6.6 + */ + public static HGetExArgs ex(Duration timeout) { + return new HGetExArgs().ex(timeout); + } + + /** + * Creates new {@link HGetExArgs} and enable {@literal EXAT}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return new {@link HGetExArgs} with {@literal EXAT} enabled. + * @see HGetExArgs#exAt(Instant) + * @since 6.6 + */ + public static HGetExArgs exAt(Instant timestamp) { + return new HGetExArgs().exAt(timestamp); + } + + /** + * Creates new {@link HGetExArgs} and enable {@literal PX}. + * + * @param timeout expire time in milliseconds. + * @return new {@link HGetExArgs} with {@literal PX} enabled. + * @see HGetExArgs#px(long) + * @since 6.6 + */ + public static HGetExArgs px(Duration timeout) { + return new HGetExArgs().px(timeout); + } + + /** + * Creates new {@link HGetExArgs} and enable {@literal PXAT}. + * + * @param timestamp the timestamp type: posix time. + * @return new {@link HGetExArgs} with {@literal PXAT} enabled. + * @see HGetExArgs#pxAt(Instant) + * @since 6.6 + */ + public static HGetExArgs pxAt(Instant timestamp) { + return new HGetExArgs().pxAt(timestamp); + } + + /** + * Creates new {@link HGetExArgs} and enable {@literal PERSIST}. + * + * @return new {@link HGetExArgs} with {@literal PERSIST} enabled. + * @see HGetExArgs#persist() + */ + public static HGetExArgs persist() { + return new HGetExArgs().persist(); + } + + } + + /** + * Set the specified expire time, in seconds. + * + * @param timeout expire time in seconds. + * @return {@code this} {@link HGetExArgs}. + * @since 6.6 + */ + public HGetExArgs ex(Duration timeout) { + + LettuceAssert.notNull(timeout, "Timeout must not be null"); + + this.ex = timeout.getSeconds(); + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return {@code this} {@link HGetExArgs}. + * @since 6.6 + */ + public HGetExArgs exAt(Instant timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + this.exAt = timestamp.getEpochSecond(); + return this; + } + + /** + * Set the specified expire time, in milliseconds. + * + * @param timeout expire time in milliseconds. + * @return {@code this} {@link HGetExArgs}. + */ + public HGetExArgs px(Duration timeout) { + + LettuceAssert.notNull(timeout, "Timeout must not be null"); + + this.px = timeout.toMillis(); + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in milliseconds. + * @return {@code this} {@link HGetExArgs}. + * @since 6.6 + */ + public HGetExArgs pxAt(Instant timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + + this.pxAt = timestamp.toEpochMilli(); + return this; + } + + /** + * Remove the time to live associated with the key. + * + * @return {@code this} {@link HGetExArgs}. + */ + public HGetExArgs persist() { + + this.persist = true; + return this; + } + + @Override + public void build(CommandArgs args) { + + if (ex != null) { + args.add("EX").add(ex); + } + + if (exAt != null) { + args.add("EXAT").add(exAt); + } + + if (px != null) { + args.add("PX").add(px); + } + + if (pxAt != null) { + args.add("PXAT").add(pxAt); + } + + if (persist) { + args.add(CommandType.PERSIST); + } + } + +} diff --git a/src/main/java/io/lettuce/core/HSetExArgs.java b/src/main/java/io/lettuce/core/HSetExArgs.java new file mode 100644 index 0000000000..40070e8bdc --- /dev/null +++ b/src/main/java/io/lettuce/core/HSetExArgs.java @@ -0,0 +1,274 @@ +/* + * Copyright 2011-Present, Redis Ltd. and Contributors + * All rights reserved. + * + * Licensed under the MIT License. + * + * This file contains contributions from third-party contributors + * 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 io.lettuce.core; + +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; + +import java.time.Duration; +import java.time.Instant; +import java.util.Date; + +import static io.lettuce.core.protocol.CommandKeyword.FNX; +import static io.lettuce.core.protocol.CommandKeyword.FXX; + +/** + * Argument list builder for the Redis HSETEX command starting from Redis 8.0. + * Static import the methods from {@link Builder} and chain the method calls: {@code ex(10).nx()}. + *

+ * {@link HSetExArgs} is a mutable object and instances should be used only once to avoid shared mutable state. + * + * @author Ivo Gaydajiev + * @since 6.6 + */ +public class HSetExArgs implements CompositeArgument { + + private Long ex; + + private Long exAt; + + private Long px; + + private Long pxAt; + + private boolean fnx = false; + + private boolean fxx = false; + + private boolean keepttl = false; + + /** + * Builder entry points for {@link HSetExArgs}. + */ + public static class Builder { + + /** + * Utility constructor. + */ + private Builder() { + } + + /** + * Creates new {@link HSetExArgs} and enable {@literal EX}. + * + * @param timeout expire time as duration. + * @return new {@link HSetExArgs} with {@literal EX} enabled. + * @see HSetExArgs#ex(long) + * @since 6.6 + */ + public static HSetExArgs ex(Duration timeout) { + return new HSetExArgs().ex(timeout); + } + + /** + * Creates new {@link HSetExArgs} and enable {@literal EXAT}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return new {@link HSetExArgs} with {@literal EXAT} enabled. + * @see HSetExArgs#exAt(Instant) + * @since 6.6 + */ + public static HSetExArgs exAt(Instant timestamp) { + return new HSetExArgs().exAt(timestamp); + } + + /** + * Creates new {@link HSetExArgs} and enable {@literal PX}. + * + * @param timeout expire time in milliseconds. + * @return new {@link HSetExArgs} with {@literal PX} enabled. + * @see HSetExArgs#px(long) + * @since 6.6 + */ + public static HSetExArgs px(Duration timeout) { + return new HSetExArgs().px(timeout); + } + + /** + * Creates new {@link HSetExArgs} and enable {@literal PXAT}. + * + * @param timestamp the timestamp type: posix time. + * @return new {@link HSetExArgs} with {@literal PXAT} enabled. + * @see HSetExArgs#pxAt(Instant) + * @since 6.6 + */ + public static HSetExArgs pxAt(Instant timestamp) { + return new HSetExArgs().pxAt(timestamp); + } + + /** + * Creates new {@link HSetExArgs} and enable {@literal NX}. + * + * @return new {@link HSetExArgs} with {@literal NX} enabled. + * @see HSetExArgs#fnx() + */ + public static HSetExArgs fnx() { + return new HSetExArgs().fnx(); + } + + /** + * Creates new {@link HSetExArgs} and enable {@literal XX}. + * + * @return new {@link HSetExArgs} with {@literal XX} enabled. + * @see HSetExArgs#fxx() + */ + public static HSetExArgs xx() { + return new HSetExArgs().fxx(); + } + + /** + * Creates new {@link HSetExArgs} and enable {@literal KEEPTTL}. + * + * @return new {@link HSetExArgs} with {@literal KEEPTTL} enabled. + * @see HSetExArgs#keepttl() + * @since 6.6 + */ + public static HSetExArgs keepttl() { + return new HSetExArgs().keepttl(); + } + + } + + /** + * Set the specified expire time, in seconds. + * + * @param timeout expire time in seconds. + * @return {@code this} {@link HSetExArgs}. + * @since 6.6 + */ + public HSetExArgs ex(Duration timeout) { + + LettuceAssert.notNull(timeout, "Timeout must not be null"); + + this.ex = timeout.getSeconds(); + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return {@code this} {@link HSetExArgs}. + * @since 6.6 + */ + public HSetExArgs exAt(Instant timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + + this.exAt = timestamp.getEpochSecond(); + return this; + } + + /** + * Set the specified expire time, in milliseconds. + * + * @param timeout expire time in milliseconds. + * @return {@code this} {@link HSetExArgs}. + */ + public HSetExArgs px(Duration timeout) { + + LettuceAssert.notNull(timeout, "Timeout must not be null"); + + this.px = timeout.toMillis(); + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in milliseconds. + * @return {@code this} {@link HSetExArgs}. + * @since 6.6 + */ + public HSetExArgs pxAt(Instant timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + + this.pxAt = timestamp.toEpochMilli(); + return this; + } + + /** + * Only set the key if it does not already exist. + * + * @return {@code this} {@link HSetExArgs}. + */ + public HSetExArgs fnx() { + + this.fnx = true; + return this; + } + + /** + * Set the value and retain the existing TTL. + * + * @return {@code this} {@link HSetExArgs}. + * @since 6.6 + */ + public HSetExArgs keepttl() { + + this.keepttl = true; + return this; + } + + /** + * Only set the key if it already exists. + * + * @return {@code this} {@link HSetExArgs}. + */ + public HSetExArgs fxx() { + + this.fxx = true; + return this; + } + + @Override + public void build(CommandArgs args) { + + if (ex != null) { + args.add("EX").add(ex); + } + + if (exAt != null) { + args.add("EXAT").add(exAt); + } + + if (px != null) { + args.add("PX").add(px); + } + + if (pxAt != null) { + args.add("PXAT").add(pxAt); + } + + if (fnx) { + args.add(FNX); + } + + if (fxx) { + args.add(FXX); + } + + if (keepttl) { + args.add("KEEPTTL"); + } + } + +} diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java index aa1ce4435b..6bcf4fa1b0 100644 --- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java +++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java @@ -1767,6 +1767,79 @@ Command hset(K key, Map map) { return createCommand(HSET, new IntegerOutput<>(codec), args); } + Command hsetex(K key, Map map) { + notNullKey(key); + LettuceAssert.notNull(map, "Map " + MUST_NOT_BE_NULL); + LettuceAssert.isTrue(!map.isEmpty(), "Map " + MUST_NOT_BE_EMPTY); + + CommandArgs args = new CommandArgs<>(codec).addKey(key); + args.add(FIELDS).add(map.size()).add(map); + + return createCommand(HSETEX, new IntegerOutput<>(codec), args); + } + + Command hsetex(K key, HSetExArgs hSetExArgs, Map map) { + notNullKey(key); + LettuceAssert.notNull(hSetExArgs, "HSetExArgs " + MUST_NOT_BE_NULL); + LettuceAssert.notNull(map, "Map " + MUST_NOT_BE_NULL); + LettuceAssert.isTrue(!map.isEmpty(), "Map " + MUST_NOT_BE_EMPTY); + + CommandArgs args = new CommandArgs<>(codec).addKey(key); + hSetExArgs.build(args); + args.add(FIELDS).add(map.size()).add(map); + + return createCommand(HSETEX, new IntegerOutput<>(codec), args); + } + + Command>> hgetex(K key, K... fields) { + keyAndFieldsProvided(key, fields); + + CommandArgs args = new CommandArgs<>(codec).addKey(key); + args.add(FIELDS).add(fields.length).addKeys(fields); + + return createCommand(HGETEX, new KeyValueListOutput<>(codec, Arrays.asList(fields)), args); + } + + Command>> hgetex(K key, HGetExArgs hGetExArgs, K... fields) { + keyAndFieldsProvided(key, fields); + LettuceAssert.notNull(hGetExArgs, "HGetExArgs " + MUST_NOT_BE_NULL); + + CommandArgs args = new CommandArgs<>(codec).addKey(key); + hGetExArgs.build(args); + args.add(FIELDS).add(fields.length).addKeys(fields); + + return createCommand(HGETEX, new KeyValueListOutput<>(codec, Arrays.asList(fields)), args); + } + + Command hgetex(KeyValueStreamingChannel channel, K key, HGetExArgs hGetExArgs, K... fields) { + keyAndFieldsProvided(key, fields); + LettuceAssert.notNull(hGetExArgs, "HGetExArgs " + MUST_NOT_BE_NULL); + notNull(channel); + + CommandArgs args = new CommandArgs<>(codec).addKey(key); + hGetExArgs.build(args); + args.add(FIELDS).add(fields.length).addKeys(fields); + + return createCommand(HGETEX, new KeyValueStreamingOutput<>(codec, channel, Arrays.asList(fields)), args); + } + + Command>> hgetdel(K key, K... fields) { + keyAndFieldsProvided(key, fields); + + CommandArgs args = new CommandArgs<>(codec).addKey(key).add(FIELDS).add(fields.length).addKeys(fields); + + return createCommand(HGETDEL, new KeyValueListOutput<>(codec, Arrays.asList(fields)), args); + } + + Command hgetdel(KeyValueStreamingChannel channel, K key, K... fields) { + keyAndFieldsProvided(key, fields); + + notNull(channel); + + CommandArgs args = new CommandArgs<>(codec).addKey(key).add(FIELDS).add(fields.length).addKeys(fields); + return createCommand(HGETDEL, new KeyValueStreamingOutput<>(codec, channel, Arrays.asList(fields)), args); + } + Command hsetnx(K key, K field, V value) { notNullKey(key); LettuceAssert.notNull(field, "Field " + MUST_NOT_BE_NULL); diff --git a/src/main/java/io/lettuce/core/api/async/RedisHashAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisHashAsyncCommands.java index ee0b8eed1a..f1fa1d9b8f 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisHashAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisHashAsyncCommands.java @@ -20,6 +20,8 @@ package io.lettuce.core.api.async; import io.lettuce.core.ExpireArgs; +import io.lettuce.core.HGetExArgs; +import io.lettuce.core.HSetExArgs; import io.lettuce.core.KeyScanCursor; import io.lettuce.core.KeyValue; import io.lettuce.core.MapScanCursor; @@ -396,6 +398,78 @@ RedisFuture hscanNovalues(KeyStreamingChannel channel, K ke */ RedisFuture hset(K key, Map map); + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + RedisFuture hsetex(K key, Map map); + + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hSetExArgs hsetex arguments. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + RedisFuture hsetex(K key, HSetExArgs hSetExArgs, Map map); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + RedisFuture>> hgetex(K key, K... fields); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + RedisFuture>> hgetex(K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return Long the number of fields that were removed from the hash. + */ + RedisFuture hgetex(KeyValueStreamingChannel channel, K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Get and delete one or more hash fields. + * + * @param key the hash key. + * @param fields fields to retrieve and delete. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + */ + RedisFuture>> hgetdel(K key, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param fields fields to retrieve and delete. + * @return Long the number of fields that were removed from the hash. + */ + RedisFuture hgetdel(KeyValueStreamingChannel channel, K key, K... fields); + /** * Set the value of a hash field, only if the field does not exist. * diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisHashReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisHashReactiveCommands.java index 99d60e7053..d7b610593e 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisHashReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisHashReactiveCommands.java @@ -20,6 +20,8 @@ package io.lettuce.core.api.reactive; import io.lettuce.core.ExpireArgs; +import io.lettuce.core.HGetExArgs; +import io.lettuce.core.HSetExArgs; import io.lettuce.core.KeyScanCursor; import io.lettuce.core.KeyValue; import io.lettuce.core.MapScanCursor; @@ -423,6 +425,78 @@ public interface RedisHashReactiveCommands { */ Mono hset(K key, Map map); + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + Mono hsetex(K key, Map map); + + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hSetExArgs hsetex arguments. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + Mono hsetex(K key, HSetExArgs hSetExArgs, Map map); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param fields fields to retrieve. + * @return KeyValue<K, V> array-reply list of fields and their values. + * @since 6.6 + */ + Flux> hgetex(K key, K... fields); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return KeyValue<K, V> array-reply list of fields and their values. + * @since 6.6 + */ + Flux> hgetex(K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return Long the number of fields that were removed from the hash. + */ + Mono hgetex(KeyValueStreamingChannel channel, K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Get and delete one or more hash fields. + * + * @param key the hash key. + * @param fields fields to retrieve and delete. + * @return KeyValue<K, V> array-reply list of fields and their values. + */ + Flux> hgetdel(K key, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param fields fields to retrieve and delete. + * @return Long the number of fields that were removed from the hash. + */ + Mono hgetdel(KeyValueStreamingChannel channel, K key, K... fields); + /** * Set the value of a hash field, only if the field does not exist. * diff --git a/src/main/java/io/lettuce/core/api/sync/RedisHashCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisHashCommands.java index 2d9f3ac313..a0a4927e0e 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisHashCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisHashCommands.java @@ -20,6 +20,8 @@ package io.lettuce.core.api.sync; import io.lettuce.core.ExpireArgs; +import io.lettuce.core.HGetExArgs; +import io.lettuce.core.HSetExArgs; import io.lettuce.core.KeyScanCursor; import io.lettuce.core.KeyValue; import io.lettuce.core.MapScanCursor; @@ -393,6 +395,78 @@ public interface RedisHashCommands { */ Long hset(K key, Map map); + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + Long hsetex(K key, Map map); + + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hSetExArgs hsetex arguments. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + Long hsetex(K key, HSetExArgs hSetExArgs, Map map); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + List> hgetex(K key, K... fields); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + List> hgetex(K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return Long the number of fields that were removed from the hash. + */ + Long hgetex(KeyValueStreamingChannel channel, K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Get and delete one or more hash fields. + * + * @param key the hash key. + * @param fields fields to retrieve and delete. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + */ + List> hgetdel(K key, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param fields fields to retrieve and delete. + * @return Long the number of fields that were removed from the hash. + */ + Long hgetdel(KeyValueStreamingChannel channel, K key, K... fields); + /** * Set the value of a hash field, only if the field does not exist. * diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionHashAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionHashAsyncCommands.java index f6e733034f..c9e13ba1de 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionHashAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionHashAsyncCommands.java @@ -20,6 +20,8 @@ package io.lettuce.core.cluster.api.async; import io.lettuce.core.ExpireArgs; +import io.lettuce.core.HGetExArgs; +import io.lettuce.core.HSetExArgs; import io.lettuce.core.KeyScanCursor; import io.lettuce.core.KeyValue; import io.lettuce.core.MapScanCursor; @@ -395,6 +397,78 @@ AsyncExecutions hscanNovalues(KeyStreamingChannel channel, */ AsyncExecutions hset(K key, Map map); + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + AsyncExecutions hsetex(K key, Map map); + + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hSetExArgs hsetex arguments. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + AsyncExecutions hsetex(K key, HSetExArgs hSetExArgs, Map map); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + AsyncExecutions>> hgetex(K key, K... fields); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + AsyncExecutions>> hgetex(K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return Long the number of fields that were removed from the hash. + */ + AsyncExecutions hgetex(KeyValueStreamingChannel channel, K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Get and delete one or more hash fields. + * + * @param key the hash key. + * @param fields fields to retrieve and delete. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + */ + AsyncExecutions>> hgetdel(K key, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param fields fields to retrieve and delete. + * @return Long the number of fields that were removed from the hash. + */ + AsyncExecutions hgetdel(KeyValueStreamingChannel channel, K key, K... fields); + /** * Set the value of a hash field, only if the field does not exist. * diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionHashCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionHashCommands.java index 05cf792d92..13dcd1d920 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionHashCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionHashCommands.java @@ -20,6 +20,8 @@ package io.lettuce.core.cluster.api.sync; import io.lettuce.core.ExpireArgs; +import io.lettuce.core.HGetExArgs; +import io.lettuce.core.HSetExArgs; import io.lettuce.core.KeyScanCursor; import io.lettuce.core.KeyValue; import io.lettuce.core.MapScanCursor; @@ -393,6 +395,78 @@ public interface NodeSelectionHashCommands { */ Executions hset(K key, Map map); + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + Executions hsetex(K key, Map map); + + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hSetExArgs hsetex arguments. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + Executions hsetex(K key, HSetExArgs hSetExArgs, Map map); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + Executions>> hgetex(K key, K... fields); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + Executions>> hgetex(K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return Long the number of fields that were removed from the hash. + */ + Executions hgetex(KeyValueStreamingChannel channel, K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Get and delete one or more hash fields. + * + * @param key the hash key. + * @param fields fields to retrieve and delete. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + */ + Executions>> hgetdel(K key, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param fields fields to retrieve and delete. + * @return Long the number of fields that were removed from the hash. + */ + Executions hgetdel(KeyValueStreamingChannel channel, K key, K... fields); + /** * Set the value of a hash field, only if the field does not exist. * diff --git a/src/main/java/io/lettuce/core/protocol/CommandKeyword.java b/src/main/java/io/lettuce/core/protocol/CommandKeyword.java index c9d782afd8..2e2e5f457b 100644 --- a/src/main/java/io/lettuce/core/protocol/CommandKeyword.java +++ b/src/main/java/io/lettuce/core/protocol/CommandKeyword.java @@ -41,7 +41,7 @@ public enum CommandKeyword implements ProtocolKeyword { IDLETIME, JUSTID, KILL, KEYSLOT, LEFT, LEN, LIMIT, LINKS, LIST, LOAD, LOG, MATCH, - MAX, MAXLEN, MEET, MIN, MINID, MINMATCHLEN, MOVED, NO, NOACK, NOCOMMANDS, NODE, NODES, NOMKSTREAM, NOPASS, NOSAVE, NOT, NOVALUES, NUMSUB, SHARDCHANNELS, SHARDNUMSUB, NUMPAT, NX, OFF, ON, ONE, OR, PAUSE, PREFIXES, + MAX, MAXLEN, MEET, MIN, MINID, MINMATCHLEN, MOVED, NO, NOACK, NOCOMMANDS, NODE, NODES, NOMKSTREAM, NOPASS, NOSAVE, NOT, NOVALUES, NUMSUB, SHARDCHANNELS, SHARDNUMSUB, NUMPAT, NX, FNX, OFF, ON, ONE, OR, PAUSE, PREFIXES, REFCOUNT, REMOVE, RELOAD, REPLACE, REDIRECT, REPLICATE, REPLICAS, REV, RESET, RESETCHANNELS, RESETKEYS, RESETPASS, @@ -49,7 +49,7 @@ public enum CommandKeyword implements ProtocolKeyword { MIGRATING, IMPORTING, SAVE, SKIPME, SLAVES, STREAM, STORE, SUM, SEGFAULT, SETUSER, TAKEOVER, TRACKING, TRACKINGINFO, TYPE, UNBLOCK, USERS, USAGE, WEIGHTS, WHOAMI, - WITHMATCHLEN, WITHSCORE, WITHSCORES, WITHVALUES, XOR, XX, YES, INDENT, NEWLINE, SPACE, GT, LT; + WITHMATCHLEN, WITHSCORE, WITHSCORES, WITHVALUES, XOR, XX, FXX, YES, INDENT, NEWLINE, SPACE, GT, LT; public final byte[] bytes; diff --git a/src/main/java/io/lettuce/core/protocol/CommandType.java b/src/main/java/io/lettuce/core/protocol/CommandType.java index 9a2fcf83f6..39d49ea5c3 100644 --- a/src/main/java/io/lettuce/core/protocol/CommandType.java +++ b/src/main/java/io/lettuce/core/protocol/CommandType.java @@ -62,7 +62,7 @@ public enum CommandType implements ProtocolKeyword { // Hash - HDEL, HEXISTS, HGET, HGETALL, HINCRBY, HINCRBYFLOAT, HKEYS, HLEN, HSTRLEN, HMGET, HMSET, HRANDFIELD, HSET, HSETNX, HVALS, HSCAN, + HDEL, HEXISTS, HGET, HGETDEL, HGETALL, HINCRBY, HINCRBYFLOAT, HKEYS, HLEN, HSTRLEN, HMGET, HMSET, HRANDFIELD, HSET, HSETEX, HGETEX, HSETNX, HVALS, HSCAN, // Transaction diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommands.kt index 3bee6f59b9..72aec71cb8 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommands.kt @@ -28,6 +28,8 @@ import io.lettuce.core.KeyValue import io.lettuce.core.MapScanCursor import io.lettuce.core.ScanArgs import io.lettuce.core.ScanCursor +import io.lettuce.core.HGetExArgs +import io.lettuce.core.HSetExArgs import java.time.Duration import java.time.Instant import java.util.* @@ -276,6 +278,37 @@ interface RedisHashCoroutinesCommands { */ suspend fun hset(key: K, map: Map): Long? + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hSetExArgs hsetex arguments. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + suspend fun hsetex(key: K, hSetExArgs: HSetExArgs, map: Map): Long? + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return List> array-reply list of fields and their values. + * @since 6.6 + */ + fun hgetex(key: K, hGetExArgs: HGetExArgs, vararg fields: K): Flow> + + /** + * Get and delete one or more hash fields. + * + * @param key the hash key. + * @param fields one or more fields whose values will be retrieved and deleted. + * @return List> array-reply list of fields and their values. + */ + fun hgetdel(key: K, vararg fields: K): Flow> + /** * Set the value of a hash field, only if the field does not exist. * diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommandsImpl.kt index 23231b5f5b..cd945c3c07 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommandsImpl.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisHashCoroutinesCommandsImpl.kt @@ -62,6 +62,13 @@ internal class RedisHashCoroutinesCommandsImpl(internal val op override fun hmget(key: K, vararg fields: K): Flow> = ops.hmget(key, *fields).asFlow() + override suspend fun hsetex(key: K, hSetExArgs: HSetExArgs, map: Map): Long? = + ops.hsetex(key, hSetExArgs, map).awaitFirstOrNull() + + override fun hgetex(key: K, hGetExArgs: HGetExArgs, vararg fields: K): Flow> = ops.hgetex(key, hGetExArgs, *fields).asFlow() + + override fun hgetdel(key: K, vararg fields: K): Flow> = ops.hgetdel(key, *fields).asFlow() + override suspend fun hrandfield(key: K): K? = ops.hrandfield(key).awaitFirstOrNull(); override suspend fun hrandfield(key: K, count: Long): List = diff --git a/src/main/templates/io/lettuce/core/api/RedisHashCommands.java b/src/main/templates/io/lettuce/core/api/RedisHashCommands.java index 461563ad6a..e0f362492a 100644 --- a/src/main/templates/io/lettuce/core/api/RedisHashCommands.java +++ b/src/main/templates/io/lettuce/core/api/RedisHashCommands.java @@ -31,6 +31,8 @@ import io.lettuce.core.MapScanCursor; import io.lettuce.core.ScanArgs; import io.lettuce.core.ScanCursor; +import io.lettuce.core.HGetExArgs; +import io.lettuce.core.HSetExArgs; import io.lettuce.core.StreamScanCursor; import io.lettuce.core.output.KeyStreamingChannel; import io.lettuce.core.output.KeyValueStreamingChannel; @@ -392,6 +394,78 @@ public interface RedisHashCommands { */ Long hset(K key, Map map); + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + Long hsetex(K key, Map map); + + /** + * Set the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hSetExArgs hsetex arguments. + * @param map the field/value pairs to update. + * @return Long long-reply: 0 if no fields were set, 1 if all the fields were set + * @since 6.6 + */ + Long hsetex(K key, HSetExArgs hSetExArgs, Map map); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + List> hgetex(K key, K... fields); + + /** + * Get the value of one or more fields of a given hash key, and optionally set their expiration + * + * @param key the key of the hash. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + * @since 6.6 + */ + List> hgetex(K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param hGetExArgs hgetex arguments. + * @param fields fields to retrieve. + * @return Long the number of fields that were removed from the hash. + */ + Long hgetex(KeyValueStreamingChannel channel, K key, HGetExArgs hGetExArgs, K... fields); + + /** + * Get and delete one or more hash fields. + * + * @param key the hash key. + * @param fields fields to retrieve and delete. + * @return List<KeyValue<K, V>> array-reply list of fields and their values. + */ + List> hgetdel(K key, K... fields); + + /** + * Stream over the values of all the given hash fields. + * + * @param channel the channel. + * @param key the key. + * @param fields fields to retrieve and delete. + * @return Long the number of fields that were removed from the hash. + */ + Long hgetdel(KeyValueStreamingChannel channel, K key, K... fields); + /** * Set the value of a hash field, only if the field does not exist. * diff --git a/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java b/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java index 2d4f1cf405..a02ae21944 100644 --- a/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java +++ b/src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -162,6 +164,63 @@ void shouldCorrectlyConstructHpttl() { + "3\r\n" + "$7\r\n" + "hField1\r\n" + "$7\r\n" + "hField2\r\n" + "$7\r\n" + "hField3\r\n"); } + @Test + void shouldCorrectlyConstructHgetdel() { + + Command command = sut.hgetdel(MY_KEY, "one", "two"); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*6\r\n" + "$7\r\n" + "HGETDEL\r\n" + "$4\r\n" + "hKey\r\n" + + "$6\r\n" + "FIELDS\r\n" + "$1\r\n" + "2\r\n" + "$3\r\n" + "one\r\n" + "$3\r\n" + "two\r\n"); + } + + @Test + void shouldCorrectlyConstructHsetex() { + Command command = sut.hsetex(MY_KEY, Collections.singletonMap("one", "1")); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*6\r\n" + "$6\r\n" + "HSETEX\r\n" + "$4\r\n" + "hKey\r\n" + + "$6\r\n" + "FIELDS\r\n" + "$1\r\n" + "1\r\n" + "$3\r\n" + "one\r\n" + "$1\r\n" + "1\r\n"); + } + + @Test + void shouldCorrectlyConstructHsetexWithArgs() { + Command command = sut.hsetex(MY_KEY, HSetExArgs.Builder.ex(Duration.ofSeconds(10)).fnx(), + Collections.singletonMap("one", "1")); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + String expected = "*9\r\n" + "$6\r\n" + "HSETEX\r\n" + "$4\r\n" + "hKey\r\n" + "$2\r\n" + "EX\r\n" + "$2\r\n" + "10\r\n" + + "$3\r\n" + "FNX\r\n" + "$6\r\n" + "FIELDS\r\n" + "$1\r\n" + "1\r\n" + "$3\r\n" + "one\r\n" + "$1\r\n" + + "1\r\n"; + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*9\r\n" + "$6\r\n" + "HSETEX\r\n" + "$4\r\n" + "hKey\r\n" + + "$2\r\n" + "EX\r\n" + "$2\r\n" + "10\r\n" + "$3\r\n" + "FNX\r\n" + "$6\r\n" + "FIELDS\r\n" + "$1\r\n" + + "1\r\n" + "$3\r\n" + "one\r\n" + "$1\r\n" + "1\r\n"); + } + + @Test + void shouldCorrectlyConstructHgetex() { + Command command = sut.hgetex(MY_KEY, "one"); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*5\r\n" + "$6\r\n" + "HGETEX\r\n" + "$4\r\n" + "hKey\r\n" + + "$6\r\n" + "FIELDS\r\n" + "$1\r\n" + "1\r\n" + "$3\r\n" + "one\r\n"); + } + + @Test + void shouldCorrectlyConstructHgetexWithArgs() { + Command command = sut.hgetex(MY_KEY, HGetExArgs.Builder.ex(Duration.ofSeconds(10)).persist(), "one"); + ByteBuf buf = Unpooled.directBuffer(); + command.encode(buf); + + assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo( + "*8\r\n" + "$6\r\n" + "HGETEX\r\n" + "$4\r\n" + "hKey\r\n" + "$2\r\n" + "EX\r\n" + "$2\r\n" + "10\r\n" + + "$7\r\n" + "PERSIST\r\n" + "$6\r\n" + "FIELDS\r\n" + "$1\r\n" + "1\r\n" + "$3\r\n" + "one\r\n"); + } + @Test void shouldCorrectlyConstructClientTrackinginfo() { diff --git a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java index 149f61e6a9..d3e2e6dba6 100644 --- a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java @@ -20,6 +20,8 @@ package io.lettuce.core.commands; import io.lettuce.core.ExpireArgs; +import io.lettuce.core.HGetExArgs; +import io.lettuce.core.HSetExArgs; import io.lettuce.core.KeyScanCursor; import io.lettuce.core.KeyValue; import io.lettuce.core.MapScanCursor; @@ -228,6 +230,96 @@ void hmgetStreaming() { assertThat(values).containsEntry("one", "1").containsEntry("two", "2"); } + @Test + @EnabledOnCommand("HGETDEL") + void hgetdel() { + setup(); + List> values = redis.hgetdel(key, "one", "two", "missing"); + assertThat(values).hasSize(3); + assertThat(values.containsAll(list(kv("one", "1"), kv("two", "2"), kv("missing", null)))).isTrue(); + assertThat(redis.hexists(key, "one")).isFalse(); + assertThat(redis.hexists(key, "two")).isFalse(); + } + + @Test + @EnabledOnCommand("HGETDEL") + void hgetdelStreaming() { + setup(); + + KeyValueStreamingAdapter streamingAdapter = new KeyValueStreamingAdapter<>(); + Long count = redis.hgetdel(streamingAdapter, key, "one", "two", "missing"); + Map values = streamingAdapter.getMap(); + assertThat(count).isEqualTo(3); + assertThat(values).hasSize(3); + assertThat(values).containsEntry("one", "1").containsEntry("two", "2").containsEntry("missing", null); + assertThat(redis.hexists(key, "one")).isFalse(); + assertThat(redis.hexists(key, "two")).isFalse(); + } + + @Test + @EnabledOnCommand("HGETEX") + void hgetex() { + setup(); + List> values = redis.hgetex(key, "one", "two", "missing"); + assertThat(values).hasSize(3); + assertThat(values.containsAll(list(kv("one", "1"), kv("two", "2"), kv("missing", null)))).isTrue(); + } + + @Test + @EnabledOnCommand("HGETEX") + void hgetexWithArgs() { + setup(); + List> values = redis.hgetex(key, HGetExArgs.Builder.ex(Duration.ofSeconds(100)), "one", "two", + "missing"); + assertThat(values).hasSize(3); + assertThat(values.containsAll(list(kv("one", "1"), kv("two", "2"), kv("missing", null)))).isTrue(); + assertThat(redis.httl(key, "one", "two")).allMatch(ttl -> ttl > 1); + } + + @Test + @EnabledOnCommand("HGETEX") + void hgetexStreaming() { + setup(); + + KeyValueStreamingAdapter streamingAdapter = new KeyValueStreamingAdapter<>(); + Long count = redis.hgetex(streamingAdapter, key, HGetExArgs.Builder.ex(Duration.ofSeconds(100)), "one", "two", + "missing"); + Map values = streamingAdapter.getMap(); + assertThat(count).isEqualTo(3); + assertThat(values).hasSize(3); + assertThat(values).containsEntry("one", "1").containsEntry("two", "2").containsEntry("missing", null); + assertThat(redis.httl(key, "one", "two")).allMatch(ttl -> ttl > 1); + } + + @Test + @EnabledOnCommand("HSETEX") + void hsetex() { + Map fields = new LinkedHashMap<>(); + fields.put("one", "1"); + fields.put("two", "2"); + + Long result = redis.hsetex(key, fields); + assertThat(result).isEqualTo(1); + + List> values = redis.hmget(key, "one", "two", "missing"); + assertThat(values.containsAll(list(kv("one", "1"), kv("two", "2")))).isTrue(); + } + + @Test + @EnabledOnCommand("HSETEX") + void hsetexWithArgs() { + Map fields = new LinkedHashMap<>(); + fields.put("one", "1"); + fields.put("two", "2"); + + Long result = redis.hsetex(key, HSetExArgs.Builder.ex(Duration.ofSeconds(100)), fields); + assertThat(result).isEqualTo(1); + + List> values = redis.hmget(key, "one", "two"); + assertThat(values.containsAll(list(kv("one", "1"), kv("two", "2")))).isTrue(); + assertThat(redis.httl(key, "one", "two")).allMatch(ttl -> ttl > 1); + } + @Test void hmset() { Map hash = new LinkedHashMap<>();