diff --git a/src/StackExchange.Redis/Enums/RedisCommand.cs b/src/StackExchange.Redis/Enums/RedisCommand.cs
index a4647d7eb..28c484fbf 100644
--- a/src/StackExchange.Redis/Enums/RedisCommand.cs
+++ b/src/StackExchange.Redis/Enums/RedisCommand.cs
@@ -70,6 +70,8 @@ internal enum RedisCommand
HEXPIREAT,
HEXPIRETIME,
HGET,
+ HGETEX,
+ HGETDEL,
HGETALL,
HINCRBY,
HINCRBYFLOAT,
@@ -85,6 +87,7 @@ internal enum RedisCommand
HRANDFIELD,
HSCAN,
HSET,
+ HSETEX,
HSETNX,
HSTRLEN,
HVALS,
@@ -289,6 +292,8 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
case RedisCommand.HDEL:
case RedisCommand.HEXPIRE:
case RedisCommand.HEXPIREAT:
+ case RedisCommand.HGETDEL:
+ case RedisCommand.HGETEX:
case RedisCommand.HINCRBY:
case RedisCommand.HINCRBYFLOAT:
case RedisCommand.HMSET:
@@ -296,6 +301,7 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
case RedisCommand.HPEXPIRE:
case RedisCommand.HPEXPIREAT:
case RedisCommand.HSET:
+ case RedisCommand.HSETEX:
case RedisCommand.HSETNX:
case RedisCommand.INCR:
case RedisCommand.INCRBY:
diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs
index 207c03326..fcf581473 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabase.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs
@@ -516,6 +516,149 @@ public interface IDatabase : IRedis, IDatabaseAsync
///
RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
+ ///
+ /// Returns the value associated with field in the hash stored at key.
+ ///
+ /// The key of the hash.
+ /// The field in the hash to get.
+ /// The flags to use for this operation.
+ /// The value associated with field, or when field is not present in the hash or key does not exist.
+ ///
+ RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Returns the value associated with field in the hash stored at key.
+ ///
+ /// The key of the hash.
+ /// The field in the hash to get.
+ /// The flags to use for this operation.
+ /// The value associated with field, or when field is not present in the hash or key does not exist.
+ ///
+ Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Returns the values associated with the specified fields in the hash stored at key.
+ /// For every field that does not exist in the hash, a value is returned.
+ /// Because non-existing keys are treated as empty hashes, running HMGET against a non-existing key will return a list of values.
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to get.
+ /// The flags to use for this operation.
+ /// List of values associated with the given fields, in the same order as they are requested.
+ ///
+ RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Gets the value of the specified hash field and sets its expiration time.
+ ///
+ /// The key of the hash.
+ /// The field in the hash to get and set the expiration for.
+ /// The expiration time to set.
+ /// If true, the expiration will be removed. And 'expiry' parameter is ignored.
+ /// The flags to use for this operation.
+ /// The value of the specified hash field.
+ RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Gets the value of the specified hash field and sets its expiration time.
+ ///
+ /// The key of the hash.
+ /// The field in the hash to get and set the expiration for.
+ /// The exact date and time to set the expiration to.
+ /// The flags to use for this operation.
+ /// The value of the specified hash field.
+ RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Gets the value of the specified hash field and sets its expiration time, returning a lease.
+ ///
+ /// The key of the hash.
+ /// The field in the hash to get and set the expiration for.
+ /// The expiration time to set.
+ /// If true, the expiration will be removed. And 'expiry' parameter is ignored.
+ /// The flags to use for this operation.
+ /// The value of the specified hash field as a lease.
+ Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Gets the value of the specified hash field and sets its expiration time, returning a lease.
+ ///
+ /// The key of the hash.
+ /// The field in the hash to get and set the expiration for.
+ /// The exact date and time to set the expiration to.
+ /// The flags to use for this operation.
+ /// The value of the specified hash field as a lease.
+ Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Gets the values of the specified hash fields and sets their expiration times.
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to get and set the expiration for.
+ /// The expiration time to set.
+ /// If true, the expiration will be removed. And 'expiry' parameter is ignored.
+ /// The flags to use for this operation.
+ /// The values of the specified hash fields.
+ RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Gets the values of the specified hash fields and sets their expiration times.
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to get and set the expiration for.
+ /// The exact date and time to set the expiration to.
+ /// The flags to use for this operation.
+ /// The values of the specified hash fields.
+ RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Sets the value of the specified hash field and sets its expiration time.
+ ///
+ /// The key of the hash.
+ /// The field in the hash to set and set the expiration for.
+ /// The value in the hash to set and set the expiration for.
+ /// The expiration time to set.
+ /// Whether to maintain the existing field's TTL (KEEPTTL flag).
+ /// Which conditions to set the value under (defaults to always).
+ /// The flags to use for this operation.
+ /// 0 if no fields were set, 1 if all the fields were set.
+ RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Sets the value of the specified hash field and sets its expiration time.
+ ///
+ /// The key of the hash.
+ /// The field in the hash to set and set the expiration for.
+ /// The value in the hash to set and set the expiration for.
+ /// The exact date and time to set the expiration to.
+ /// Which conditions to set the value under (defaults to always).
+ /// The flags to use for this operation.
+ /// 0 if no fields were set, 1 if all the fields were set.
+ RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Sets the values of the specified hash fields and sets their expiration times.
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to set and set the expiration for.
+ /// The expiration time to set.
+ /// Whether to maintain the existing fields' TTL (KEEPTTL flag).
+ /// Which conditions to set the values under (defaults to always).
+ /// The flags to use for this operation.
+ /// 0 if no fields were set, 1 if all the fields were set.
+ RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Sets the values of the specified hash fields and sets their expiration times.
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to set and set the expiration for.
+ /// The exact date and time to set the expiration to.
+ /// Which conditions to set the values under (defaults to always).
+ /// The flags to use for this operation.
+ /// 0 if no fields were set, 1 if all the fields were set.
+ RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None);
+
///
/// Returns all fields and values of the hash stored at key.
///
diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
index 9852c131c..71ce7f789 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
@@ -84,6 +84,45 @@ public interface IDatabaseAsync : IRedisAsync
///
Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
+ ///
+ Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None);
+
///
Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
index b97bba73b..b2e9a9031 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
@@ -84,6 +84,45 @@ public Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFla
public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) =>
Inner.HashExistsAsync(ToInner(key), hashField, flags);
+ public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndDeleteAsync(key, hashField, flags);
+
+ public Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetLeaseAndDeleteAsync(key, hashField, flags);
+
+ public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndDeleteAsync(key, hashFields, flags);
+
+ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndSetExpiryAsync(key, hashField, expiry, persist, flags);
+
+ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndSetExpiryAsync(key, hashField, expiry, flags);
+
+ public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, persist, flags);
+
+ public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, flags);
+
+ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, persist, flags);
+
+ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, flags);
+
+ public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldSetAndSetExpiryAsync(key, field, value, expiry, keepTtl, when, flags);
+
+ public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldSetAndSetExpiryAsync(key, field, value, expiry, when, flags);
+
+ public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, keepTtl, when, flags);
+
+ public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, when, flags);
+
public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags);
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
index 75d93d0f9..3f760ec96 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
@@ -81,6 +81,45 @@ public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags =
public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) =>
Inner.HashExists(ToInner(key), hashField, flags);
+ public RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndDelete(key, hashField, flags);
+
+ public Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetLeaseAndDelete(key, hashField, flags);
+
+ public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndDelete(key, hashFields, flags);
+
+ public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndSetExpiry(key, hashField, expiry, persist, flags);
+
+ public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndSetExpiry(key, hashField, expiry, flags);
+
+ public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, persist, flags);
+
+ public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, flags);
+
+ public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndSetExpiry(key, hashFields, expiry, persist, flags);
+
+ public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldGetAndSetExpiry(key, hashFields, expiry, flags);
+
+ public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldSetAndSetExpiry(key, field, value, expiry, keepTtl, when, flags);
+
+ public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldSetAndSetExpiry(key, field, value, expiry, when, flags);
+
+ public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldSetAndSetExpiry(key, hashFields, expiry, keepTtl, when, flags);
+
+ public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldSetAndSetExpiry(key, hashFields, expiry, when, flags);
+
public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags);
diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs
index b89a6b946..c6f4d0f83 100644
--- a/src/StackExchange.Redis/Message.cs
+++ b/src/StackExchange.Redis/Message.cs
@@ -310,6 +310,9 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, i
public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) =>
new CommandValueValueValueValueValueMessage(db, flags, command, value0, value1, value2, value3, value4);
+ public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue[] values) =>
+ new CommandKeyValueValueValuesMessage(db, flags, command, key, value0, value1, values);
+
public static Message Create(
int db,
CommandFlags flags,
@@ -1177,6 +1180,36 @@ protected override void WriteImpl(PhysicalConnection physical)
public override int ArgCount => values.Length + 1;
}
+ private sealed class CommandKeyValueValueValuesMessage : CommandKeyBase
+ {
+ private readonly RedisValue value0;
+ private readonly RedisValue value1;
+ private readonly RedisValue[] values;
+ public CommandKeyValueValueValuesMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, RedisValue[] values) : base(db, flags, command, key)
+ {
+ for (int i = 0; i < values.Length; i++)
+ {
+ values[i].AssertNotNull();
+ }
+
+ value0.AssertNotNull();
+ value1.AssertNotNull();
+ this.value0 = value0;
+ this.value1 = value1;
+ this.values = values;
+ }
+
+ protected override void WriteImpl(PhysicalConnection physical)
+ {
+ physical.WriteHeader(Command, values.Length + 3);
+ physical.Write(Key);
+ physical.WriteBulkString(value0);
+ physical.WriteBulkString(value1);
+ for (int i = 0; i < values.Length; i++) physical.WriteBulkString(values[i]);
+ }
+ public override int ArgCount => values.Length + 3;
+ }
+
private sealed class CommandKeyValueValueMessage : CommandKeyBase
{
private readonly RedisValue value0, value1;
diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
index a24333c8e..0f36c5ad0 100644
--- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
+++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
@@ -1893,4 +1893,29 @@ virtual StackExchange.Redis.RedisResult.Length.get -> int
virtual StackExchange.Redis.RedisResult.this[int index].get -> StackExchange.Redis.RedisResult!
StackExchange.Redis.ConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void
StackExchange.Redis.IConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void
-
+StackExchange.Redis.IDatabase.HashFieldGetAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
+StackExchange.Redis.IDatabase.HashFieldGetAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
+StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
+StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
+StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
+StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
+StackExchange.Redis.IDatabase.HashFieldGetLeaseAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease?
+StackExchange.Redis.IDatabase.HashFieldGetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease?
+StackExchange.Redis.IDatabase.HashFieldGetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease?
+StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
+StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
+StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
+StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
+StackExchange.Redis.IDatabaseAsync.HashFieldGetAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>!
+StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs
index 7468bdb64..4ccc8b421 100644
--- a/src/StackExchange.Redis/RedisDatabase.cs
+++ b/src/StackExchange.Redis/RedisDatabase.cs
@@ -446,6 +446,337 @@ private T HashFieldExecute(RedisCommand cmd, RedisKey key, Custom
private Task AsyncCustomArrExecutor(Message msg, TProcessor processor) where TProcessor : ResultProcessor => ExecuteAsync(msg, processor)!;
+ public RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField);
+ return ExecuteSync(msg, ResultProcessor.RedisValueFromArray);
+ }
+
+ public Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField);
+ return ExecuteSync(msg, ResultProcessor.LeaseFromArray);
+ }
+
+ public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ if (hashFields.Length == 0) return Array.Empty();
+ var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, hashFields.Length, hashFields);
+ return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty());
+ }
+
+ public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField);
+ return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray);
+ }
+
+ public Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField);
+ return ExecuteAsync(msg, ResultProcessor.LeaseFromArray);
+ }
+
+ public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState);
+ var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, hashFields.Length, hashFields);
+ return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty());
+ }
+
+ private delegate void CalculateExpiryArgs(T expiry, out RedisValue precision, out RedisValue time);
+
+ private void CalculateExpiryValues(TimeSpan expiry, out RedisValue precision, out RedisValue time)
+ {
+ long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond;
+ var useSeconds = milliseconds % 1000 == 0;
+ precision = useSeconds ? RedisLiterals.EX : RedisLiterals.PX;
+ time = useSeconds ? (milliseconds / 1000) : milliseconds;
+ }
+
+ private void CalculateExpiryValues(DateTime expiry, out RedisValue precision, out RedisValue time)
+ {
+ long milliseconds = GetMillisecondsUntil(expiry);
+ var useSeconds = milliseconds % 1000 == 0;
+ precision = useSeconds ? RedisLiterals.EXAT : RedisLiterals.PXAT;
+ time = useSeconds ? (milliseconds / 1000) : milliseconds;
+ }
+
+ private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue hashField, T? expiry, CalculateExpiryArgs calculateExpiryArgs, bool persist, CommandFlags flags) where T : struct
+ {
+ if (expiry != null && persist) throw new ArgumentException("Cannot specify both expiry and persist");
+
+ if (persist) // Case when persist is true (expiry is disregarded)
+ {
+ return Message.Create(Database, flags, RedisCommand.HGETEX, key, RedisLiterals.PERSIST, RedisLiterals.FIELDS, 1, hashField);
+ }
+
+ if (expiry != null) // Check if expiry is not null
+ {
+ calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time);
+ return Message.Create(Database, flags, RedisCommand.HGETEX, key, precision, time, RedisLiterals.FIELDS, 1, hashField);
+ }
+
+ // Default case when neither expiry nor persist are set
+ return Message.Create(Database, flags, RedisCommand.HGETEX, RedisLiterals.FIELDS, 1, hashField);
+ }
+
+ private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, bool persist, CommandFlags flags) where T : struct
+ {
+ if (expiry != null && persist) throw new ArgumentException("Cannot specify both expiry and persist");
+
+ // Calculate the total size of the array based on conditions
+ int arraySize = 0;
+ if (persist) // Case when persist is true (expiry is disregarded)
+ {
+ arraySize = 3; // PERSIST, FIELDS, hashFields.Length
+ }
+ else if (expiry != null) // Case when expiry is not null
+ {
+ arraySize = 4; // precision, time, FIELDS, hashFields.Length
+ }
+ else // Default case when both expiry and persist are default
+ {
+ arraySize = 2; // FIELDS, hashFields.Length
+ }
+
+ // Create an array to hold the values (including hashFields)
+ RedisValue[] values = new RedisValue[arraySize + hashFields.Length];
+
+ int index = 0;
+ // Add PERSIST or expiry values, or just FIELDS
+ if (persist) // Case when persist is true (expiry is disregarded)
+ {
+ values[index++] = RedisLiterals.PERSIST;
+ }
+ else if (expiry != null) // Check if expiry is not null
+ {
+ calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time);
+ values[index++] = precision;
+ values[index++] = time;
+ }
+ values[index++] = RedisLiterals.FIELDS;
+ values[index++] = hashFields.Length;
+ // Add hash fields to the array
+ Array.Copy(hashFields, 0, values, index, hashFields.Length);
+ return Message.Create(Database, flags, RedisCommand.HGETEX, key, values);
+ }
+
+ public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags);
+ return ExecuteSync(msg, ResultProcessor.RedisValueFromArray);
+ }
+
+ public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags);
+ return ExecuteSync(msg, ResultProcessor.RedisValueFromArray);
+ }
+
+ public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags);
+ return ExecuteSync(msg, ResultProcessor.LeaseFromArray);
+ }
+
+ public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags);
+ return ExecuteSync(msg, ResultProcessor.LeaseFromArray);
+ }
+
+ public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ if (hashFields.Length == 0) return Array.Empty();
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags);
+ return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty());
+ }
+
+ public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ if (hashFields.Length == 0) return Array.Empty();
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, false, flags);
+ return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty());
+ }
+
+ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags);
+ return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray);
+ }
+
+ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags);
+ return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray);
+ }
+
+ public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags);
+ return ExecuteAsync(msg, ResultProcessor.LeaseFromArray);
+ }
+
+ public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags);
+ return ExecuteAsync(msg, ResultProcessor.LeaseFromArray);
+ }
+
+ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState);
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags);
+ return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty());
+ }
+
+ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState);
+ var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, false, flags);
+ return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty());
+ }
+
+ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue field, RedisValue value, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct
+ {
+ if (expiry != null && keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl");
+
+ if (when == When.Always)
+ {
+ if (keepTtl) // Case when keepTtl is true (expiry is disregarded)
+ {
+ return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, field, value);
+ }
+
+ if (expiry != null) // Case when expiry is not null
+ {
+ calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time);
+ return Message.Create(Database, flags, RedisCommand.HSETEX, key, precision, time, RedisLiterals.FIELDS, 1, field, value);
+ }
+ // Default case when both expiry and keepTtl are default
+ return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.FIELDS, 1, field, value);
+ }
+
+ var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX;
+ if (keepTtl) // Case when keepTtl is true (expiry is disregarded)
+ {
+ return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, field, value);
+ }
+ if (expiry != null) // Case when expiry is not null
+ {
+ calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time);
+ return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, precision, time, RedisLiterals.FIELDS, 1, field, value);
+ }
+ // Only existance is specified
+ return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.FIELDS, 1, field, value);
+ }
+
+ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, HashEntry[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct
+ {
+ if (expiry != null && keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl");
+
+ // Determine the base array size
+ int arraySize = when == When.Always ? 0 : 1;
+
+ if (keepTtl)
+ {
+ arraySize += 3; // KEEPTTL, FIELDS, hashFields.Length
+ }
+ else if (expiry != null)
+ {
+ arraySize += 4; // precision, time, FIELDS, hashFields.Length
+ }
+ else
+ {
+ arraySize += 2; // FIELDS, hashFields.Length
+ }
+
+ arraySize += hashFields.Length * 2;
+ RedisValue[] values = new RedisValue[arraySize];
+ int index = 0;
+
+ if (when != When.Always)
+ {
+ values[index++] = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX;
+ }
+
+ if (keepTtl) // Case when keepTtl is true (expiry is disregarded)
+ {
+ values[index++] = RedisLiterals.KEEPTTL;
+ }
+ else if (expiry != null) // Case when expiry is not null
+ {
+ calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time);
+ values[index++] = precision;
+ values[index++] = time;
+ }
+ values[index++] = RedisLiterals.FIELDS;
+ values[index++] = hashFields.Length;
+ for (int i = 0; i < hashFields.Length; i++)
+ {
+ values[index++] = hashFields[i].name;
+ values[index++] = hashFields[i].value;
+ }
+ return Message.Create(Database, flags, RedisCommand.HSETEX, key, values);
+ }
+
+ public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldSetAndSetExpiryMessage(key, field, value, expiry, CalculateExpiryValues, when, keepTtl, flags);
+ return ExecuteSync(msg, ResultProcessor.RedisValue);
+ }
+
+ public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldSetAndSetExpiryMessage(key, field, value, (DateTime?)expiry, CalculateExpiryValues, when, false, flags);
+ return ExecuteSync(msg, ResultProcessor.RedisValue);
+ }
+
+ public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags);
+ return ExecuteSync(msg, ResultProcessor.RedisValue);
+ }
+ public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, when, false, flags);
+ return ExecuteSync(msg, ResultProcessor.RedisValue);
+ }
+
+ public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldSetAndSetExpiryMessage(key, field, value, expiry, CalculateExpiryValues, when, keepTtl, flags);
+ return ExecuteAsync(msg, ResultProcessor.RedisValue);
+ }
+
+ public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = HashFieldSetAndSetExpiryMessage(key, field, value, (DateTime?)expiry, CalculateExpiryValues, when, false, flags);
+ return ExecuteAsync(msg, ResultProcessor.RedisValue);
+ }
+
+ public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags);
+ return ExecuteAsync(msg, ResultProcessor.RedisValue);
+ }
+ public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, when, false, flags);
+ return ExecuteAsync(msg, ResultProcessor.RedisValue);
+ }
+
public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) =>
HashFieldExecute(RedisCommand.HPEXPIRETIME, key, SyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields);
diff --git a/src/StackExchange.Redis/RedisFeatures.cs b/src/StackExchange.Redis/RedisFeatures.cs
index 225516433..ffc06cf7a 100644
--- a/src/StackExchange.Redis/RedisFeatures.cs
+++ b/src/StackExchange.Redis/RedisFeatures.cs
@@ -44,7 +44,8 @@ namespace StackExchange.Redis
v7_0_0_rc1 = new Version(6, 9, 240), // 7.0 RC1 is version 6.9.240
v7_2_0_rc1 = new Version(7, 1, 240), // 7.2 RC1 is version 7.1.240
v7_4_0_rc1 = new Version(7, 3, 240), // 7.4 RC1 is version 7.3.240
- v7_4_0_rc2 = new Version(7, 3, 241); // 7.4 RC2 is version 7.3.241
+ v7_4_0_rc2 = new Version(7, 3, 241), // 7.4 RC2 is version 7.3.241
+ v8_0_0_M04 = new Version(7, 9, 227); // 8.0 M04 is version 7.9.227
#pragma warning restore SA1310 // Field names should not contain underscore
#pragma warning restore SA1311 // Static readonly fields should begin with upper-case letter
diff --git a/src/StackExchange.Redis/RedisLiterals.cs b/src/StackExchange.Redis/RedisLiterals.cs
index 549691fd2..d70c55296 100644
--- a/src/StackExchange.Redis/RedisLiterals.cs
+++ b/src/StackExchange.Redis/RedisLiterals.cs
@@ -81,7 +81,9 @@ public static readonly RedisValue
FIELDS = "FIELDS",
FILTERBY = "FILTERBY",
FLUSH = "FLUSH",
+ FNX = "FNX",
FREQ = "FREQ",
+ FXX = "FXX",
GET = "GET",
GETKEYS = "GETKEYS",
GETNAME = "GETNAME",
diff --git a/src/StackExchange.Redis/ResultProcessor.cs b/src/StackExchange.Redis/ResultProcessor.cs
index 648387b87..b80983e85 100644
--- a/src/StackExchange.Redis/ResultProcessor.cs
+++ b/src/StackExchange.Redis/ResultProcessor.cs
@@ -87,9 +87,15 @@ public static readonly ResultProcessor
public static readonly ResultProcessor
RedisValue = new RedisValueProcessor();
+ public static readonly ResultProcessor
+ RedisValueFromArray = new RedisValueFromArrayProcessor();
+
public static readonly ResultProcessor>
Lease = new LeaseProcessor();
+ public static readonly ResultProcessor>
+ LeaseFromArray = new LeaseFromArrayProcessor();
+
public static readonly ResultProcessor
RedisValueArray = new RedisValueArrayProcessor();
@@ -1835,6 +1841,25 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
}
}
+ private sealed class RedisValueFromArrayProcessor : ResultProcessor
+ {
+ protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
+ {
+ switch (result.Resp2TypeBulkString)
+ {
+ case ResultType.Array:
+ var items = result.GetItems();
+ if (items.Length == 1)
+ { // treat an array of 1 like a single reply
+ SetResult(message, items[0].AsRedisValue());
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+ }
+
private sealed class RoleProcessor : ResultProcessor
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
@@ -1980,6 +2005,25 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
}
}
+ private sealed class LeaseFromArrayProcessor : ResultProcessor>
+ {
+ protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
+ {
+ switch (result.Resp2TypeBulkString)
+ {
+ case ResultType.Array:
+ var items = result.GetItems();
+ if (items.Length == 1)
+ { // treat an array of 1 like a single reply
+ SetResult(message, items[0].AsLease()!);
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+ }
+
private class ScriptResultProcessor : ResultProcessor
{
public override bool SetResult(PhysicalConnection connection, Message message, in RawResult result)
diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs
index e50cd0546..dde3b3485 100644
--- a/tests/StackExchange.Redis.Tests/HashFieldTests.cs
+++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
@@ -19,6 +20,8 @@ public class HashFieldTests : TestBase
private readonly RedisValue[] fields = new RedisValue[] { "f1", "f2" };
+ private readonly RedisValue[] values = new RedisValue[] { 1, 2 };
+
public HashFieldTests(ITestOutputHelper output, SharedConnectionFixture fixture) : base(output, fixture)
{
}
@@ -302,4 +305,267 @@ public void HashFieldPersistNoField()
var fieldsResult = db.HashFieldPersist(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" });
Assert.Equal(new[] { PersistResult.NoSuchField, PersistResult.NoSuchField }, fieldsResult);
}
+
+ [Fact]
+ public void HashFieldGetAndSetExpiry()
+ {
+ var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase();
+ var hashKey = Me();
+
+ // testing with timespan
+ db.HashSet(hashKey, entries);
+ var fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", TimeSpan.FromHours(1));
+ Assert.Equal(1, fieldResult);
+ var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing with datetime
+ db.HashSet(hashKey, entries);
+ fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", DateTime.Now.AddMinutes(120));
+ Assert.Equal(1, fieldResult);
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing persist
+ fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", persist: true);
+ Assert.Equal(1, fieldResult);
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.Equal(-1, fieldTtl);
+
+ // testing multiple fields with timespan
+ db.HashSet(hashKey, entries);
+ var fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, TimeSpan.FromHours(1));
+ Assert.Equal(values, fieldResults);
+ var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing multiple fields with datetime
+ db.HashSet(hashKey, entries);
+ fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, DateTime.Now.AddMinutes(120));
+ Assert.Equal(values, fieldResults);
+ fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing multiple fields with persist
+ fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, persist: true);
+ Assert.Equal(values, fieldResults);
+ fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.Equal(new long[] { -1, -1 }, fieldTtls);
+ }
+
+ [Fact]
+ public async void HashFieldGetAndSetExpiryAsync()
+ {
+ var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase();
+ var hashKey = Me();
+
+ // testing with timespan
+ db.HashSet(hashKey, entries);
+ var fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", TimeSpan.FromHours(1));
+ Assert.Equal(1, fieldResult);
+ var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing with datetime
+ db.HashSet(hashKey, entries);
+ fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", DateTime.Now.AddMinutes(120));
+ Assert.Equal(1, fieldResult);
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing persist
+ fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", persist: true);
+ Assert.Equal(1, fieldResult);
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.Equal(-1, fieldTtl);
+
+ // testing multiple fields with timespan
+ db.HashSet(hashKey, entries);
+ var fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, TimeSpan.FromHours(1));
+ Assert.Equal(values, fieldResults);
+ var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing multiple fields with datetime
+ db.HashSet(hashKey, entries);
+ fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, DateTime.Now.AddMinutes(120));
+ Assert.Equal(values, fieldResults);
+ fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing multiple fields with persist
+ fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, persist: true);
+ Assert.Equal(values, fieldResults);
+ fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.Equal(new long[] { -1, -1 }, fieldTtls);
+ }
+
+ [Fact]
+ public void HashFieldSetAndSetExpiry()
+ {
+ var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase();
+ var hashKey = Me();
+
+ // testing with timespan
+ var result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, TimeSpan.FromHours(1));
+ Assert.Equal(1, result);
+ var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing with datetime
+ result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, DateTime.Now.AddMinutes(120));
+ Assert.Equal(1, result);
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing with keepttl
+ result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, keepTtl: true);
+ Assert.Equal(1, result);
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing multiple fields with timespan
+ result = db.HashFieldSetAndSetExpiry(hashKey, entries, TimeSpan.FromHours(1));
+ Assert.Equal(1, result);
+ var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing multiple fields with datetime
+ result = db.HashFieldSetAndSetExpiry(hashKey, entries, DateTime.Now.AddMinutes(120));
+ Assert.Equal(1, result);
+ fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing multiple fields with keepttl
+ result = db.HashFieldSetAndSetExpiry(hashKey, entries, keepTtl: true);
+ Assert.Equal(1, result);
+ fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing with ExpireWhen.Exists
+ db.KeyDelete(hashKey);
+ result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.Exists);
+ Assert.Equal(0, result); // should not set because it doesnt exist
+
+ // testing with ExpireWhen.NotExists
+ result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.NotExists);
+ Assert.Equal(1, result); // should set because it doesnt exist
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing with ExpireWhen.GreaterThanCurrentExpiry
+ result = db.HashFieldSetAndSetExpiry(hashKey, "f1", -1, keepTtl: true, when: When.Exists);
+ Assert.Equal(1, result); // should set because it exists
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+ }
+
+ [Fact]
+ public async Task HashFieldSetAndSetExpiryAsync()
+ {
+ var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase();
+ var hashKey = Me();
+
+ // testing with timespan
+ var result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, TimeSpan.FromHours(1));
+ Assert.Equal(1, result);
+ var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing with datetime
+ result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, DateTime.Now.AddMinutes(120));
+ Assert.Equal(1, result);
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing with keepttl
+ result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, keepTtl: true);
+ Assert.Equal(1, result);
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing multiple fields with timespan
+ result = await db.HashFieldSetAndSetExpiryAsync(hashKey, entries, TimeSpan.FromHours(1));
+ Assert.Equal(1, result);
+ var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing multiple fields with datetime
+ result = await db.HashFieldSetAndSetExpiryAsync(hashKey, entries, DateTime.Now.AddMinutes(120));
+ Assert.Equal(1, result);
+ fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing multiple fields with keepttl
+ result = await db.HashFieldSetAndSetExpiryAsync(hashKey, entries, keepTtl: true);
+ Assert.Equal(1, result);
+ fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+ Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds);
+
+ // testing with ExpireWhen.Exists
+ db.KeyDelete(hashKey);
+ result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.Exists);
+ Assert.Equal(0, result); // should not set because it doesnt exist
+
+ // testing with ExpireWhen.NotExists
+ result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.NotExists);
+ Assert.Equal(1, result); // should set because it doesnt exist
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+
+ // testing with ExpireWhen.GreaterThanCurrentExpiry
+ result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", -1, keepTtl: true, when: When.Exists);
+ Assert.Equal(1, result); // should set because it exists
+ fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0];
+ Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds);
+ }
+ [Fact]
+ public void HashFieldGetAndDelete()
+ {
+ var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase();
+ var hashKey = Me();
+
+ // single field
+ db.HashSet(hashKey, entries);
+ var fieldResult = db.HashFieldGetAndDelete(hashKey, "f1");
+ Assert.Equal(1, fieldResult);
+ Assert.False(db.HashExists(hashKey, "f1"));
+
+ // multiple fields
+ db.HashSet(hashKey, entries);
+ var fieldResults = db.HashFieldGetAndDelete(hashKey, fields);
+ Assert.Equal(values, fieldResults);
+ Assert.False(db.HashExists(hashKey, "f1"));
+ Assert.False(db.HashExists(hashKey, "f2"));
+ }
+
+ [Fact]
+ public async void HashFieldGetAndDeleteAsync()
+ {
+ var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase();
+ var hashKey = Me();
+
+ // single field
+ db.HashSet(hashKey, entries);
+ var fieldResult = await db.HashFieldGetAndDeleteAsync(hashKey, "f1");
+ Assert.Equal(1, fieldResult);
+ Assert.False(db.HashExists(hashKey, "f1"));
+
+ // multiple fields
+ db.HashSet(hashKey, entries);
+ var fieldResults = await db.HashFieldGetAndDeleteAsync(hashKey, fields);
+ Assert.Equal(values, fieldResults);
+ Assert.False(db.HashExists(hashKey, "f1"));
+ Assert.False(db.HashExists(hashKey, "f2"));
+ }
}