Skip to content

Commit

Permalink
Feature: Support for hash field expiration (#2716)
Browse files Browse the repository at this point in the history
Closes/Fixes #2715 

This PR add support for a set of new commands related to expiration of individual members of hash:

> 	**_HashFieldExpire_** exposes the functionality of commands HPEXPIRE/HPEXPIREAT, for each specified field, it gets the value and sets the field's remaining time to live or expireation timestamp
> 	**_HashFieldExpireTime_** exposes the functionality of command HPEXPIRETIME, for specified field, it gets the remaining time to live in milliseconds or expiration timestamp
> 	**_HashFieldPersist_** exposes the functionality of command HPERSIST, for each specified field, it removes the expiration time
> 	**_HashFieldTimeToLive_** expoes the functionality of command HPTTL, for specified field, it gets the remaining time to live in milliseconds or expiration timestamp

---------

Co-authored-by: Nick Craver <[email protected]>
Co-authored-by: Nick Craver <[email protected]>
  • Loading branch information
3 people authored Aug 17, 2024
1 parent 8346a5c commit e208905
Show file tree
Hide file tree
Showing 14 changed files with 721 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 27 additions & 0 deletions src/StackExchange.Redis/Enums/ExpireResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace StackExchange.Redis;

/// <summary>
/// Specifies the result of operation to set expire time.
/// </summary>
public enum ExpireResult
{
/// <summary>
/// Field deleted because the specified expiration time is due.
/// </summary>
Due = 2,

/// <summary>
/// Expiration time/duration updated successfully.
/// </summary>
Success = 1,

/// <summary>
/// Expiration not set because of a specified NX | XX | GT | LT condition not met.
/// </summary>
ConditionNotMet = 0,

/// <summary>
/// No such field.
/// </summary>
NoSuchField = -2,
}
22 changes: 22 additions & 0 deletions src/StackExchange.Redis/Enums/PersistResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace StackExchange.Redis;

/// <summary>
/// Specifies the result of operation to remove the expire time.
/// </summary>
public enum PersistResult
{
/// <summary>
/// Expiration removed successfully.
/// </summary>
Success = 1,

/// <summary>
/// Expiration not removed because of a specified NX | XX | GT | LT condition not met.
/// </summary>
ConditionNotMet = -1,

/// <summary>
/// No such field.
/// </summary>
NoSuchField = -2,
}
16 changes: 16 additions & 0 deletions src/StackExchange.Redis/Enums/RedisCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ internal enum RedisCommand
HDEL,
HELLO,
HEXISTS,
HEXPIRE,
HEXPIREAT,
HEXPIRETIME,
HGET,
HGETALL,
HINCRBY,
Expand All @@ -74,6 +77,11 @@ internal enum RedisCommand
HLEN,
HMGET,
HMSET,
HPERSIST,
HPEXPIRE,
HPEXPIREAT,
HPEXPIRETIME,
HPTTL,
HRANDFIELD,
HSCAN,
HSET,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
159 changes: 159 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,165 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks><seealso href="https://redis.io/commands/hexists"/></remarks>
bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);

/// <summary>
/// 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.
/// </summary>
/// <param name="key">The key of the hash.</param>
/// <param name="hashFields">The fields in the hash to set expire time.</param>
/// <param name="expiry">The timeout to set.</param>
/// <param name="when">under which condition the expiration will be set using <see cref="ExpireWhen"/>.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// Empty array if the key does not exist. Otherwise returns an array where each item is the result of operation for given fields:
/// <list type="table">
/// <listheader>
/// <term>Result</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>2</term>
/// <description>Field deleted because the specified expiration time is due.</description>
/// </item>
/// <item>
/// <term>1</term>
/// <description>Expiration time set/updated.</description>
/// </item>
/// <item>
/// <term>0</term>
/// <description>Expiration time is not set/update (a specified ExpireWhen condition is not met).</description>
/// </item>
/// <item>
/// <term>-1</term>
/// <description>No such field exists.</description>
/// </item>
/// </list>
/// </returns>
ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);

/// <summary>
/// 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.
/// </summary>
/// <param name="key">The key of the hash.</param>
/// <param name="hashFields">The fields in the hash to set expire time.</param>
/// <param name="expiry">The exact date to expiry to set.</param>
/// <param name="when">under which condition the expiration will be set using <see cref="ExpireWhen"/>.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// Empty array if the key does not exist. Otherwise returns an array where each item is the result of operation for given fields:
/// <list type="table">
/// <listheader>
/// <term>Result</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>2</term>
/// <description>Field deleted because the specified expiration time is due.</description>
/// </item>
/// <item>
/// <term>1</term>
/// <description>Expiration time set/updated.</description>
/// </item>
/// <item>
/// <term>0</term>
/// <description>Expiration time is not set/update (a specified ExpireWhen condition is not met).</description>
/// </item>
/// <item>
/// <term>-1</term>
/// <description>No such field exists.</description>
/// </item>
/// </list>
/// </returns>
ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);

/// <summary>
/// For each specified field, it gets the expiration time as a Unix timestamp in milliseconds (milliseconds since the Unix epoch).
/// </summary>
/// <param name="key">The key of the hash.</param>
/// <param name="hashFields">The fields in the hash to get expire time.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// Empty array if the key does not exist. Otherwise returns the result of operation for given fields:
/// <list type="table">
/// <listheader>
/// <term>Result</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>&gt; 0</term>
/// <description>Expiration time, as a Unix timestamp in milliseconds.</description>
/// </item>
/// <item>
/// <term>-1</term>
/// <description>Field has no associated expiration time.</description>
/// </item>
/// <item>
/// <term>-2</term>
/// <description>No such field exists.</description>
/// </item>
/// </list>
/// </returns>
long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);

/// <summary>
/// For each specified field, it removes the expiration time.
/// </summary>
/// <param name="key">The key of the hash.</param>
/// <param name="hashFields">The fields in the hash to remove expire time.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// Empty array if the key does not exist. Otherwise returns the result of operation for given fields:
/// <list type="table">
/// <listheader>
/// <term>Result</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>1</term>
/// <description>Expiration time was removed.</description>
/// </item>
/// <item>
/// <term>-1</term>
/// <description>Field has no associated expiration time.</description>
/// </item>
/// <item>
/// <term>-2</term>
/// <description>No such field exists.</description>
/// </item>
/// </list>
/// </returns>
PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);

/// <summary>
/// For each specified field, it gets the remaining time to live in milliseconds.
/// </summary>
/// <param name="key">The key of the hash.</param>
/// <param name="hashFields">The fields in the hash to get expire time.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// Empty array if the key does not exist. Otherwise returns the result of operation for given fields:
/// <list type="table">
/// <listheader>
/// <term>Result</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>&gt; 0</term>
/// <description>Time to live, in milliseconds.</description>
/// </item>
/// <item>
/// <term>-1</term>
/// <description>Field has no associated expiration time.</description>
/// </item>
/// <item>
/// <term>-2</term>
/// <description>No such field exists.</description>
/// </item>
/// </list>
/// </returns>
long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns the value associated with field in the hash stored at key.
/// </summary>
Expand Down
15 changes: 15 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ public interface IDatabaseAsync : IRedisAsync
/// <inheritdoc cref="IDatabase.HashExists(RedisKey, RedisValue, CommandFlags)"/>
Task<bool> HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.HashFieldExpire(RedisKey, RedisValue[], TimeSpan, ExpireWhen, CommandFlags)"/>
Task<ExpireResult[]> HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.HashFieldExpire(RedisKey, RedisValue[], DateTime, ExpireWhen, CommandFlags)"/>
Task<ExpireResult[]> HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.HashFieldGetExpireDateTime(RedisKey, RedisValue[], CommandFlags)"/>
Task<long[]> HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="HashFieldPersistAsync(RedisKey, RedisValue[], CommandFlags)"/>
Task<PersistResult[]> HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.HashFieldGetTimeToLive(RedisKey, RedisValue[], CommandFlags)"/>
Task<long[]> HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.HashGet(RedisKey, RedisValue, CommandFlags)"/>
Task<RedisValue> HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);

Expand Down
15 changes: 15 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ public Task<bool> HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFla
public Task<bool> HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) =>
Inner.HashExistsAsync(ToInner(key), hashField, flags);

public Task<ExpireResult[]> 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<ExpireResult[]> 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<long[]> HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
Inner.HashFieldGetExpireDateTimeAsync(ToInner(key), hashFields, flags);

public Task<PersistResult[]> HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
Inner.HashFieldPersistAsync(ToInner(key), hashFields, flags);

public Task<long[]> HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
Inner.HashFieldGetTimeToLiveAsync(ToInner(key), hashFields, flags);

public Task<HashEntry[]> HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
Inner.HashGetAllAsync(ToInner(key), flags);

Expand Down
15 changes: 15 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading

0 comments on commit e208905

Please sign in to comment.