diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index 5a1b9aa64..bf057512f 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -8,6 +8,7 @@ Current package versions:
## Unreleased
+- Add support for hash field expiration (see [#2715](https://github.com/StackExchange/StackExchange.Redis/issues/2715)) ([#2716 by atakavci](https://github.com/StackExchange/StackExchange.Redis/pull/2716]))
## 2.8.0
diff --git a/src/StackExchange.Redis/Enums/ExpireResult.cs b/src/StackExchange.Redis/Enums/ExpireResult.cs
new file mode 100644
index 000000000..6211492e6
--- /dev/null
+++ b/src/StackExchange.Redis/Enums/ExpireResult.cs
@@ -0,0 +1,27 @@
+namespace StackExchange.Redis;
+
+///
+/// Specifies the result of operation to set expire time.
+///
+public enum ExpireResult
+{
+ ///
+ /// Field deleted because the specified expiration time is due.
+ ///
+ Due = 2,
+
+ ///
+ /// Expiration time/duration updated successfully.
+ ///
+ Success = 1,
+
+ ///
+ /// Expiration not set because of a specified NX | XX | GT | LT condition not met.
+ ///
+ ConditionNotMet = 0,
+
+ ///
+ /// No such field.
+ ///
+ NoSuchField = -2,
+}
diff --git a/src/StackExchange.Redis/Enums/PersistResult.cs b/src/StackExchange.Redis/Enums/PersistResult.cs
new file mode 100644
index 000000000..91fdf9fa7
--- /dev/null
+++ b/src/StackExchange.Redis/Enums/PersistResult.cs
@@ -0,0 +1,22 @@
+namespace StackExchange.Redis;
+
+///
+/// Specifies the result of operation to remove the expire time.
+///
+public enum PersistResult
+{
+ ///
+ /// Expiration removed successfully.
+ ///
+ Success = 1,
+
+ ///
+ /// Expiration not removed because of a specified NX | XX | GT | LT condition not met.
+ ///
+ ConditionNotMet = -1,
+
+ ///
+ /// No such field.
+ ///
+ NoSuchField = -2,
+}
diff --git a/src/StackExchange.Redis/Enums/RedisCommand.cs b/src/StackExchange.Redis/Enums/RedisCommand.cs
index 728b88a76..a4647d7eb 100644
--- a/src/StackExchange.Redis/Enums/RedisCommand.cs
+++ b/src/StackExchange.Redis/Enums/RedisCommand.cs
@@ -66,6 +66,9 @@ internal enum RedisCommand
HDEL,
HELLO,
HEXISTS,
+ HEXPIRE,
+ HEXPIREAT,
+ HEXPIRETIME,
HGET,
HGETALL,
HINCRBY,
@@ -74,6 +77,11 @@ internal enum RedisCommand
HLEN,
HMGET,
HMSET,
+ HPERSIST,
+ HPEXPIRE,
+ HPEXPIREAT,
+ HPEXPIRETIME,
+ HPTTL,
HRANDFIELD,
HSCAN,
HSET,
@@ -279,9 +287,14 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
case RedisCommand.GETEX:
case RedisCommand.GETSET:
case RedisCommand.HDEL:
+ case RedisCommand.HEXPIRE:
+ case RedisCommand.HEXPIREAT:
case RedisCommand.HINCRBY:
case RedisCommand.HINCRBYFLOAT:
case RedisCommand.HMSET:
+ case RedisCommand.HPERSIST:
+ case RedisCommand.HPEXPIRE:
+ case RedisCommand.HPEXPIREAT:
case RedisCommand.HSET:
case RedisCommand.HSETNX:
case RedisCommand.INCR:
@@ -378,11 +391,14 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
case RedisCommand.GETRANGE:
case RedisCommand.HELLO:
case RedisCommand.HEXISTS:
+ case RedisCommand.HEXPIRETIME:
case RedisCommand.HGET:
case RedisCommand.HGETALL:
case RedisCommand.HKEYS:
case RedisCommand.HLEN:
case RedisCommand.HMGET:
+ case RedisCommand.HPEXPIRETIME:
+ case RedisCommand.HPTTL:
case RedisCommand.HRANDFIELD:
case RedisCommand.HSCAN:
case RedisCommand.HSTRLEN:
diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs
index 6178051d0..c192f07f9 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabase.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs
@@ -325,6 +325,165 @@ public interface IDatabase : IRedis, IDatabaseAsync
///
bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
+ ///
+ /// Set the remaining time to live in milliseconds for the given set of fields of hash
+ /// After the timeout has expired, the field of the hash will automatically be deleted.
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to set expire time.
+ /// The timeout to set.
+ /// under which condition the expiration will be set using .
+ /// The flags to use for this operation.
+ ///
+ /// Empty array if the key does not exist. Otherwise returns an array where each item is the result of operation for given fields:
+ ///
+ ///
+ /// Result
+ /// Description
+ ///
+ /// -
+ /// 2
+ /// Field deleted because the specified expiration time is due.
+ ///
+ /// -
+ /// 1
+ /// Expiration time set/updated.
+ ///
+ /// -
+ /// 0
+ /// Expiration time is not set/update (a specified ExpireWhen condition is not met).
+ ///
+ /// -
+ /// -1
+ /// No such field exists.
+ ///
+ ///
+ ///
+ ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Set the time out on a field of the given set of fields of hash.
+ /// After the timeout has expired, the field of the hash will automatically be deleted.
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to set expire time.
+ /// The exact date to expiry to set.
+ /// under which condition the expiration will be set using .
+ /// The flags to use for this operation.
+ ///
+ /// Empty array if the key does not exist. Otherwise returns an array where each item is the result of operation for given fields:
+ ///
+ ///
+ /// Result
+ /// Description
+ ///
+ /// -
+ /// 2
+ /// Field deleted because the specified expiration time is due.
+ ///
+ /// -
+ /// 1
+ /// Expiration time set/updated.
+ ///
+ /// -
+ /// 0
+ /// Expiration time is not set/update (a specified ExpireWhen condition is not met).
+ ///
+ /// -
+ /// -1
+ /// No such field exists.
+ ///
+ ///
+ ///
+ ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// For each specified field, it gets the expiration time as a Unix timestamp in milliseconds (milliseconds since the Unix epoch).
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to get expire time.
+ /// The flags to use for this operation.
+ ///
+ /// Empty array if the key does not exist. Otherwise returns the result of operation for given fields:
+ ///
+ ///
+ /// Result
+ /// Description
+ ///
+ /// -
+ /// > 0
+ /// Expiration time, as a Unix timestamp in milliseconds.
+ ///
+ /// -
+ /// -1
+ /// Field has no associated expiration time.
+ ///
+ /// -
+ /// -2
+ /// No such field exists.
+ ///
+ ///
+ ///
+ long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// For each specified field, it removes the expiration time.
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to remove expire time.
+ /// The flags to use for this operation.
+ ///
+ /// Empty array if the key does not exist. Otherwise returns the result of operation for given fields:
+ ///
+ ///
+ /// Result
+ /// Description
+ ///
+ /// -
+ /// 1
+ /// Expiration time was removed.
+ ///
+ /// -
+ /// -1
+ /// Field has no associated expiration time.
+ ///
+ /// -
+ /// -2
+ /// No such field exists.
+ ///
+ ///
+ ///
+ PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// For each specified field, it gets the remaining time to live in milliseconds.
+ ///
+ /// The key of the hash.
+ /// The fields in the hash to get expire time.
+ /// The flags to use for this operation.
+ ///
+ /// Empty array if the key does not exist. Otherwise returns the result of operation for given fields:
+ ///
+ ///
+ /// Result
+ /// Description
+ ///
+ /// -
+ /// > 0
+ /// Time to live, in milliseconds.
+ ///
+ /// -
+ /// -1
+ /// Field has no associated expiration time.
+ ///
+ /// -
+ /// -2
+ /// No such field exists.
+ ///
+ ///
+ ///
+ long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
+
///
/// Returns the value associated with field in the hash stored at key.
///
diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
index 103b7151b..8600a5a1a 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
@@ -84,6 +84,21 @@ public interface IDatabaseAsync : IRedisAsync
///
Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
+ ///
+ Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
+
+ ///
+ Task HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
+
///
Task HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
index f6821af1d..f18e74512 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
@@ -84,6 +84,21 @@ 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 HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags);
+
+ public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags);
+
+ public Task HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
+ Inner.HashFieldGetExpireDateTimeAsync(ToInner(key), hashFields, flags);
+
+ public Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
+ Inner.HashFieldPersistAsync(ToInner(key), hashFields, flags);
+
+ public Task HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
+ Inner.HashFieldGetTimeToLiveAsync(ToInner(key), hashFields, flags);
+
public Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
Inner.HashGetAllAsync(ToInner(key), flags);
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
index e2b484935..8f570edd6 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
@@ -81,6 +81,21 @@ 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 ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags);
+
+ public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
+ Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags);
+
+ public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
+ Inner.HashFieldGetExpireDateTime(ToInner(key), hashFields, flags);
+
+ public PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
+ Inner.HashFieldPersist(ToInner(key), hashFields, flags);
+
+ public long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
+ Inner.HashFieldGetTimeToLive(ToInner(key), hashFields, flags);
+
public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) =>
Inner.HashGetAll(ToInner(key), flags);
diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
index 8707cc1b4..7b0a515df 100644
--- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
+++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
@@ -412,6 +412,11 @@ StackExchange.Redis.Exclude.Both = StackExchange.Redis.Exclude.Start | StackExch
StackExchange.Redis.Exclude.None = 0 -> StackExchange.Redis.Exclude
StackExchange.Redis.Exclude.Start = 1 -> StackExchange.Redis.Exclude
StackExchange.Redis.Exclude.Stop = 2 -> StackExchange.Redis.Exclude
+StackExchange.Redis.ExpireResult
+StackExchange.Redis.ExpireResult.ConditionNotMet = 0 -> StackExchange.Redis.ExpireResult
+StackExchange.Redis.ExpireResult.Due = 2 -> StackExchange.Redis.ExpireResult
+StackExchange.Redis.ExpireResult.NoSuchField = -2 -> StackExchange.Redis.ExpireResult
+StackExchange.Redis.ExpireResult.Success = 1 -> StackExchange.Redis.ExpireResult
StackExchange.Redis.ExpireWhen
StackExchange.Redis.ExpireWhen.Always = 0 -> StackExchange.Redis.ExpireWhen
StackExchange.Redis.ExpireWhen.GreaterThanCurrentExpiry = 1 -> StackExchange.Redis.ExpireWhen
@@ -558,6 +563,11 @@ StackExchange.Redis.IDatabase.HashDecrement(StackExchange.Redis.RedisKey key, St
StackExchange.Redis.IDatabase.HashDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
StackExchange.Redis.IDatabase.HashDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.HashExists(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
+StackExchange.Redis.IDatabase.HashFieldExpire(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ExpireResult[]!
+StackExchange.Redis.IDatabase.HashFieldExpire(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ExpireResult[]!
+StackExchange.Redis.IDatabase.HashFieldGetExpireDateTime(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long[]!
+StackExchange.Redis.IDatabase.HashFieldGetTimeToLive(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long[]!
+StackExchange.Redis.IDatabase.HashFieldPersist(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.PersistResult[]!
StackExchange.Redis.IDatabase.HashGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
StackExchange.Redis.IDatabase.HashGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
StackExchange.Redis.IDatabase.HashGetAll(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.HashEntry[]!
@@ -789,6 +799,13 @@ StackExchange.Redis.IDatabaseAsync.HashDecrementAsync(StackExchange.Redis.RedisK
StackExchange.Redis.IDatabaseAsync.HashDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.HashDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.HashExistsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+
+StackExchange.Redis.IDatabaseAsync.HashFieldExpireAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldExpireAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetExpireDateTimeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldGetTimeToLiveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.HashFieldPersistAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+
StackExchange.Redis.IDatabaseAsync.HashGetAllAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.HashGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.HashGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
@@ -1248,6 +1265,10 @@ StackExchange.Redis.NameValueEntry.Value.get -> StackExchange.Redis.RedisValue
StackExchange.Redis.Order
StackExchange.Redis.Order.Ascending = 0 -> StackExchange.Redis.Order
StackExchange.Redis.Order.Descending = 1 -> StackExchange.Redis.Order
+StackExchange.Redis.PersistResult
+StackExchange.Redis.PersistResult.ConditionNotMet = -1 -> StackExchange.Redis.PersistResult
+StackExchange.Redis.PersistResult.NoSuchField = -2 -> StackExchange.Redis.PersistResult
+StackExchange.Redis.PersistResult.Success = 1 -> StackExchange.Redis.PersistResult
StackExchange.Redis.Profiling.IProfiledCommand
StackExchange.Redis.Profiling.IProfiledCommand.Command.get -> string!
StackExchange.Redis.Profiling.IProfiledCommand.CommandCreated.get -> System.DateTime
diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs
index 288e263d2..0e5eada53 100644
--- a/src/StackExchange.Redis/RedisDatabase.cs
+++ b/src/StackExchange.Redis/RedisDatabase.cs
@@ -387,6 +387,83 @@ public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFla
return ExecuteAsync(msg, ResultProcessor.Boolean);
}
+ public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
+ {
+ long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond;
+ return HashFieldExpireExecute(key, milliseconds, when, PickExpireCommandByPrecision, SyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields);
+ }
+
+ public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
+ {
+ long milliseconds = GetMillisecondsUntil(expiry);
+ return HashFieldExpireExecute(key, milliseconds, when, PickExpireAtCommandByPrecision, SyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields);
+ }
+
+ public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
+ {
+ long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond;
+ return HashFieldExpireExecute(key, milliseconds, when, PickExpireCommandByPrecision, AsyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields);
+ }
+
+ public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
+ {
+ long milliseconds = GetMillisecondsUntil(expiry);
+ return HashFieldExpireExecute(key, milliseconds, when, PickExpireAtCommandByPrecision, AsyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields);
+ }
+
+ private T HashFieldExpireExecute(RedisKey key, long milliseconds, ExpireWhen when, Func getCmd, CustomExecutor executor, TProcessor processor, CommandFlags flags, params RedisValue[] hashFields)
+ {
+ if (hashFields == null) throw new ArgumentNullException(nameof(hashFields));
+ var useSeconds = milliseconds % 1000 == 0;
+ var cmd = getCmd(useSeconds);
+ long expiry = useSeconds ? (milliseconds / 1000) : milliseconds;
+
+ var values = when switch
+ {
+ ExpireWhen.Always => new List { expiry, RedisLiterals.FIELDS, hashFields.Length },
+ _ => new List { expiry, when.ToLiteral(), RedisLiterals.FIELDS, hashFields.Length },
+ };
+ values.AddRange(hashFields);
+ var msg = Message.Create(Database, flags, cmd, key, values.ToArray());
+ return executor(msg, processor);
+ }
+
+ private static RedisCommand PickExpireCommandByPrecision(bool useSeconds) => useSeconds ? RedisCommand.HEXPIRE : RedisCommand.HPEXPIRE;
+
+ private static RedisCommand PickExpireAtCommandByPrecision(bool useSeconds) => useSeconds ? RedisCommand.HEXPIREAT : RedisCommand.HPEXPIREAT;
+
+ private T HashFieldExecute(RedisCommand cmd, RedisKey key, CustomExecutor executor, TProcessor processor, CommandFlags flags = CommandFlags.None, params RedisValue[] hashFields)
+ {
+ var values = new List { RedisLiterals.FIELDS, hashFields.Length };
+ values.AddRange(hashFields);
+ var msg = Message.Create(Database, flags, cmd, key, values.ToArray());
+ return executor(msg, processor);
+ }
+
+ private delegate T CustomExecutor(Message msg, TProcessor processor);
+
+ private T[] SyncCustomArrExecutor(Message msg, TProcessor processor) where TProcessor : ResultProcessor => ExecuteSync(msg, processor)!;
+
+ private Task AsyncCustomArrExecutor(Message msg, TProcessor processor) where TProcessor : ResultProcessor => ExecuteAsync(msg, processor)!;
+
+ public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) =>
+ HashFieldExecute(RedisCommand.HPEXPIRETIME, key, SyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields);
+
+ public Task HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) =>
+ HashFieldExecute(RedisCommand.HPEXPIRETIME, key, AsyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields);
+
+ public PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) =>
+ HashFieldExecute(RedisCommand.HPERSIST, key, SyncCustomArrExecutor>, ResultProcessor.PersistResultArray, flags, hashFields);
+
+ public Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) =>
+ HashFieldExecute(RedisCommand.HPERSIST, key, AsyncCustomArrExecutor>, ResultProcessor.PersistResultArray, flags, hashFields);
+
+ public long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) =>
+ HashFieldExecute(RedisCommand.HPTTL, key, SyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields);
+
+ public Task HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) =>
+ HashFieldExecute(RedisCommand.HPTTL, key, AsyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields);
+
public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField);
diff --git a/src/StackExchange.Redis/RedisFeatures.cs b/src/StackExchange.Redis/RedisFeatures.cs
index e81043010..225516433 100644
--- a/src/StackExchange.Redis/RedisFeatures.cs
+++ b/src/StackExchange.Redis/RedisFeatures.cs
@@ -43,7 +43,8 @@ namespace StackExchange.Redis
v6_2_0 = new Version(6, 2, 0),
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_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
#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 b77649402..a076ff3fe 100644
--- a/src/StackExchange.Redis/RedisLiterals.cs
+++ b/src/StackExchange.Redis/RedisLiterals.cs
@@ -78,6 +78,7 @@ public static readonly RedisValue
EX = "EX",
EXAT = "EXAT",
EXISTS = "EXISTS",
+ FIELDS = "FIELDS",
FILTERBY = "FILTERBY",
FLUSH = "FLUSH",
FREQ = "FREQ",
diff --git a/src/StackExchange.Redis/ResultProcessor.cs b/src/StackExchange.Redis/ResultProcessor.cs
index 894e6f5f9..7a69f7429 100644
--- a/src/StackExchange.Redis/ResultProcessor.cs
+++ b/src/StackExchange.Redis/ResultProcessor.cs
@@ -68,6 +68,10 @@ public static readonly ResultProcessor
public static readonly ResultProcessor
NullableInt64 = new NullableInt64Processor();
+ public static readonly ResultProcessor ExpireResultArray = new ExpireResultArrayProcessor();
+
+ public static readonly ResultProcessor PersistResultArray = new PersistResultArrayProcessor();
+
public static readonly ResultProcessor
RedisChannelArrayLiteral = new RedisChannelArrayProcessor(RedisChannel.PatternMode.Literal);
@@ -1452,6 +1456,47 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return true;
}
break;
+ case ResultType.Array:
+ var items = result.GetItems();
+ if (items.Length == 1)
+ { // treat an array of 1 like a single reply
+ if (items[0].TryGetInt64(out long value))
+ {
+ SetResult(message, value);
+ return true;
+ }
+ }
+ break;
+ }
+ return false;
+ }
+ }
+
+ private sealed class ExpireResultArrayProcessor : ResultProcessor
+ {
+ protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
+ {
+ if (result.Resp2TypeArray == ResultType.Array || result.IsNull)
+ {
+ var arr = result.ToArray((in RawResult x) => (ExpireResult)(long)x.AsRedisValue())!;
+
+ SetResult(message, arr);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private sealed class PersistResultArrayProcessor : ResultProcessor
+ {
+ protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
+ {
+ if (result.Resp2TypeArray == ResultType.Array || result.IsNull)
+ {
+ var arr = result.ToArray((in RawResult x) => (PersistResult)(long)x.AsRedisValue())!;
+
+ SetResult(message, arr);
+ return true;
}
return false;
}
diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs
new file mode 100644
index 000000000..e50cd0546
--- /dev/null
+++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs
@@ -0,0 +1,305 @@
+using System;
+using System.Linq;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace StackExchange.Redis.Tests;
+
+///
+/// Tests for .
+///
+[RunPerProtocol]
+[Collection(SharedConnectionFixture.Key)]
+public class HashFieldTests : TestBase
+{
+ private readonly DateTime nextCentury = new DateTime(2101, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ private readonly TimeSpan oneYearInMs = TimeSpan.FromMilliseconds(31536000000);
+
+ private readonly HashEntry[] entries = new HashEntry[] { new("f1", 1), new("f2", 2) };
+
+ private readonly RedisValue[] fields = new RedisValue[] { "f1", "f2" };
+
+ public HashFieldTests(ITestOutputHelper output, SharedConnectionFixture fixture) : base(output, fixture)
+ {
+ }
+
+ [Fact]
+ public void HashFieldExpire()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+
+ var fieldsResult = db.HashFieldExpire(hashKey, fields, oneYearInMs);
+ Assert.Equal(new[] { ExpireResult.Success, ExpireResult.Success }, fieldsResult);
+
+ fieldsResult = db.HashFieldExpire(hashKey, fields, nextCentury);
+ Assert.Equal(new[] { ExpireResult.Success, ExpireResult.Success, }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldExpireNoKey()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase();
+ var hashKey = Me();
+
+ var fieldsResult = db.HashFieldExpire(hashKey, fields, oneYearInMs);
+ Assert.Equal(new[] { ExpireResult.NoSuchField, ExpireResult.NoSuchField }, fieldsResult);
+
+ fieldsResult = db.HashFieldExpire(hashKey, fields, nextCentury);
+ Assert.Equal(new[] { ExpireResult.NoSuchField, ExpireResult.NoSuchField }, fieldsResult);
+ }
+
+ [Fact]
+ public async void HashFieldExpireAsync()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+
+ var fieldsResult = await db.HashFieldExpireAsync(hashKey, fields, oneYearInMs);
+ Assert.Equal(new[] { ExpireResult.Success, ExpireResult.Success }, fieldsResult);
+
+ fieldsResult = await db.HashFieldExpireAsync(hashKey, fields, nextCentury);
+ Assert.Equal(new[] { ExpireResult.Success, ExpireResult.Success }, fieldsResult);
+ }
+
+ [Fact]
+ public async void HashFieldExpireAsyncNoKey()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase();
+ var hashKey = Me();
+
+ var fieldsResult = await db.HashFieldExpireAsync(hashKey, fields, oneYearInMs);
+ Assert.Equal(new[] { ExpireResult.NoSuchField, ExpireResult.NoSuchField }, fieldsResult);
+
+ fieldsResult = await db.HashFieldExpireAsync(hashKey, fields, nextCentury);
+ Assert.Equal(new[] { ExpireResult.NoSuchField, ExpireResult.NoSuchField }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldGetExpireDateTimeIsDue()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+
+ var result = db.HashFieldExpire(hashKey, new RedisValue[] { "f1" }, new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc));
+ Assert.Equal(new[] { ExpireResult.Due }, result);
+ }
+
+ [Fact]
+ public void HashFieldExpireNoField()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+
+ var result = db.HashFieldExpire(hashKey, new RedisValue[] { "nonExistingField" }, oneYearInMs);
+ Assert.Equal(new[] { ExpireResult.NoSuchField }, result);
+ }
+
+ [Fact]
+ public void HashFieldExpireConditionsSatisfied()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.KeyDelete(hashKey);
+ db.HashSet(hashKey, entries);
+ db.HashSet(hashKey, new HashEntry[] { new("f3", 3), new("f4", 4) });
+ var initialExpire = db.HashFieldExpire(hashKey, new RedisValue[] { "f2", "f3", "f4" }, new DateTime(2050, 1, 1, 0, 0, 0, DateTimeKind.Utc));
+ Assert.Equal(new[] { ExpireResult.Success, ExpireResult.Success, ExpireResult.Success }, initialExpire);
+
+ var result = db.HashFieldExpire(hashKey, new RedisValue[] { "f1" }, oneYearInMs, ExpireWhen.HasNoExpiry);
+ Assert.Equal(new[] { ExpireResult.Success }, result);
+
+ result = db.HashFieldExpire(hashKey, new RedisValue[] { "f2" }, oneYearInMs, ExpireWhen.HasExpiry);
+ Assert.Equal(new[] { ExpireResult.Success }, result);
+
+ result = db.HashFieldExpire(hashKey, new RedisValue[] { "f3" }, nextCentury, ExpireWhen.GreaterThanCurrentExpiry);
+ Assert.Equal(new[] { ExpireResult.Success }, result);
+
+ result = db.HashFieldExpire(hashKey, new RedisValue[] { "f4" }, oneYearInMs, ExpireWhen.LessThanCurrentExpiry);
+ Assert.Equal(new[] { ExpireResult.Success }, result);
+ }
+
+ [Fact]
+ public void HashFieldExpireConditionsNotSatisfied()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.KeyDelete(hashKey);
+ db.HashSet(hashKey, entries);
+ db.HashSet(hashKey, new HashEntry[] { new("f3", 3), new("f4", 4) });
+ var initialExpire = db.HashFieldExpire(hashKey, new RedisValue[] { "f2", "f3", "f4" }, new DateTime(2050, 1, 1, 0, 0, 0, DateTimeKind.Utc));
+ Assert.Equal(new[] { ExpireResult.Success, ExpireResult.Success, ExpireResult.Success }, initialExpire);
+
+ var result = db.HashFieldExpire(hashKey, new RedisValue[] { "f1" }, oneYearInMs, ExpireWhen.HasExpiry);
+ Assert.Equal(new[] { ExpireResult.ConditionNotMet }, result);
+
+ result = db.HashFieldExpire(hashKey, new RedisValue[] { "f2" }, oneYearInMs, ExpireWhen.HasNoExpiry);
+ Assert.Equal(new[] { ExpireResult.ConditionNotMet }, result);
+
+ result = db.HashFieldExpire(hashKey, new RedisValue[] { "f3" }, nextCentury, ExpireWhen.LessThanCurrentExpiry);
+ Assert.Equal(new[] { ExpireResult.ConditionNotMet }, result);
+
+ result = db.HashFieldExpire(hashKey, new RedisValue[] { "f4" }, oneYearInMs, ExpireWhen.GreaterThanCurrentExpiry);
+ Assert.Equal(new[] { ExpireResult.ConditionNotMet }, result);
+ }
+
+ [Fact]
+ public void HashFieldGetExpireDateTime()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+ db.HashFieldExpire(hashKey, fields, nextCentury);
+ long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds();
+
+ var result = db.HashFieldGetExpireDateTime(hashKey, new RedisValue[] { "f1" });
+ Assert.Equal(new[] { ms }, result);
+
+ var fieldsResult = db.HashFieldGetExpireDateTime(hashKey, fields);
+ Assert.Equal(new[] { ms, ms }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldExpireFieldNoExpireTime()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+
+ var result = db.HashFieldGetExpireDateTime(hashKey, new RedisValue[] { "f1" });
+ Assert.Equal(new[] { -1L }, result);
+
+ var fieldsResult = db.HashFieldGetExpireDateTime(hashKey, fields);
+ Assert.Equal(new long[] { -1, -1, }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldGetExpireDateTimeNoKey()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase();
+ var hashKey = Me();
+
+ var fieldsResult = db.HashFieldGetExpireDateTime(hashKey, fields);
+ Assert.Equal(new long[] { -2, -2, }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldGetExpireDateTimeNoField()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+ db.HashFieldExpire(hashKey, fields, oneYearInMs);
+
+ var fieldsResult = db.HashFieldGetExpireDateTime(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" });
+ Assert.Equal(new long[] { -2, -2, }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldGetTimeToLive()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+ db.HashFieldExpire(hashKey, fields, oneYearInMs);
+ long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds();
+
+ var result = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" });
+ Assert.NotNull(result);
+ Assert.True(result.Length == 1);
+ Assert.True(result[0] > 0);
+
+ var fieldsResult = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.NotNull(fieldsResult);
+ Assert.True(fieldsResult.Length > 0);
+ Assert.True(fieldsResult.All(x => x > 0));
+ }
+
+ [Fact]
+ public void HashFieldGetTimeToLiveNoExpireTime()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+
+ var fieldsResult = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.Equal(new long[] { -1, -1, }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldGetTimeToLiveNoKey()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase();
+ var hashKey = Me();
+
+ var fieldsResult = db.HashFieldGetTimeToLive(hashKey, fields);
+ Assert.Equal(new long[] { -2, -2, }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldGetTimeToLiveNoField()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+ db.HashFieldExpire(hashKey, fields, oneYearInMs);
+
+ var fieldsResult = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" });
+ Assert.Equal(new long[] { -2, -2, }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldPersist()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+ db.HashFieldExpire(hashKey, fields, oneYearInMs);
+ long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds();
+
+ var result = db.HashFieldPersist(hashKey, new RedisValue[] { "f1" });
+ Assert.Equal(new[] { PersistResult.Success }, result);
+
+ db.HashFieldExpire(hashKey, fields, oneYearInMs);
+
+ var fieldsResult = db.HashFieldPersist(hashKey, fields);
+ Assert.Equal(new[] { PersistResult.Success, PersistResult.Success }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldPersistNoExpireTime()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+
+ var fieldsResult = db.HashFieldPersist(hashKey, fields);
+ Assert.Equal(new[] { PersistResult.ConditionNotMet, PersistResult.ConditionNotMet }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldPersistNoKey()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase();
+ var hashKey = Me();
+
+ var fieldsResult = db.HashFieldPersist(hashKey, fields);
+ Assert.Equal(new[] { PersistResult.NoSuchField, PersistResult.NoSuchField }, fieldsResult);
+ }
+
+ [Fact]
+ public void HashFieldPersistNoField()
+ {
+ var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase();
+ var hashKey = Me();
+ db.HashSet(hashKey, entries);
+ db.HashFieldExpire(hashKey, fields, oneYearInMs);
+
+ var fieldsResult = db.HashFieldPersist(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" });
+ Assert.Equal(new[] { PersistResult.NoSuchField, PersistResult.NoSuchField }, fieldsResult);
+ }
+}