Skip to content

Commit e208905

Browse files
atakavciNickCraver
andauthored
Feature: Support for hash field expiration (#2716)
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 <nickcraver@microsoft.com> Co-authored-by: Nick Craver <nrcraver@gmail.com>
1 parent 8346a5c commit e208905

14 files changed

+721
-1
lines changed

docs/ReleaseNotes.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Current package versions:
88

99
## Unreleased
1010

11+
- 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]))
1112

1213
## 2.8.0
1314

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace StackExchange.Redis;
2+
3+
/// <summary>
4+
/// Specifies the result of operation to set expire time.
5+
/// </summary>
6+
public enum ExpireResult
7+
{
8+
/// <summary>
9+
/// Field deleted because the specified expiration time is due.
10+
/// </summary>
11+
Due = 2,
12+
13+
/// <summary>
14+
/// Expiration time/duration updated successfully.
15+
/// </summary>
16+
Success = 1,
17+
18+
/// <summary>
19+
/// Expiration not set because of a specified NX | XX | GT | LT condition not met.
20+
/// </summary>
21+
ConditionNotMet = 0,
22+
23+
/// <summary>
24+
/// No such field.
25+
/// </summary>
26+
NoSuchField = -2,
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace StackExchange.Redis;
2+
3+
/// <summary>
4+
/// Specifies the result of operation to remove the expire time.
5+
/// </summary>
6+
public enum PersistResult
7+
{
8+
/// <summary>
9+
/// Expiration removed successfully.
10+
/// </summary>
11+
Success = 1,
12+
13+
/// <summary>
14+
/// Expiration not removed because of a specified NX | XX | GT | LT condition not met.
15+
/// </summary>
16+
ConditionNotMet = -1,
17+
18+
/// <summary>
19+
/// No such field.
20+
/// </summary>
21+
NoSuchField = -2,
22+
}

src/StackExchange.Redis/Enums/RedisCommand.cs

+16
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ internal enum RedisCommand
6666
HDEL,
6767
HELLO,
6868
HEXISTS,
69+
HEXPIRE,
70+
HEXPIREAT,
71+
HEXPIRETIME,
6972
HGET,
7073
HGETALL,
7174
HINCRBY,
@@ -74,6 +77,11 @@ internal enum RedisCommand
7477
HLEN,
7578
HMGET,
7679
HMSET,
80+
HPERSIST,
81+
HPEXPIRE,
82+
HPEXPIREAT,
83+
HPEXPIRETIME,
84+
HPTTL,
7785
HRANDFIELD,
7886
HSCAN,
7987
HSET,
@@ -279,9 +287,14 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
279287
case RedisCommand.GETEX:
280288
case RedisCommand.GETSET:
281289
case RedisCommand.HDEL:
290+
case RedisCommand.HEXPIRE:
291+
case RedisCommand.HEXPIREAT:
282292
case RedisCommand.HINCRBY:
283293
case RedisCommand.HINCRBYFLOAT:
284294
case RedisCommand.HMSET:
295+
case RedisCommand.HPERSIST:
296+
case RedisCommand.HPEXPIRE:
297+
case RedisCommand.HPEXPIREAT:
285298
case RedisCommand.HSET:
286299
case RedisCommand.HSETNX:
287300
case RedisCommand.INCR:
@@ -378,11 +391,14 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
378391
case RedisCommand.GETRANGE:
379392
case RedisCommand.HELLO:
380393
case RedisCommand.HEXISTS:
394+
case RedisCommand.HEXPIRETIME:
381395
case RedisCommand.HGET:
382396
case RedisCommand.HGETALL:
383397
case RedisCommand.HKEYS:
384398
case RedisCommand.HLEN:
385399
case RedisCommand.HMGET:
400+
case RedisCommand.HPEXPIRETIME:
401+
case RedisCommand.HPTTL:
386402
case RedisCommand.HRANDFIELD:
387403
case RedisCommand.HSCAN:
388404
case RedisCommand.HSTRLEN:

src/StackExchange.Redis/Interfaces/IDatabase.cs

+159
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,165 @@ public interface IDatabase : IRedis, IDatabaseAsync
325325
/// <remarks><seealso href="https://redis.io/commands/hexists"/></remarks>
326326
bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
327327

328+
/// <summary>
329+
/// Set the remaining time to live in milliseconds for the given set of fields of hash
330+
/// After the timeout has expired, the field of the hash will automatically be deleted.
331+
/// </summary>
332+
/// <param name="key">The key of the hash.</param>
333+
/// <param name="hashFields">The fields in the hash to set expire time.</param>
334+
/// <param name="expiry">The timeout to set.</param>
335+
/// <param name="when">under which condition the expiration will be set using <see cref="ExpireWhen"/>.</param>
336+
/// <param name="flags">The flags to use for this operation.</param>
337+
/// <returns>
338+
/// Empty array if the key does not exist. Otherwise returns an array where each item is the result of operation for given fields:
339+
/// <list type="table">
340+
/// <listheader>
341+
/// <term>Result</term>
342+
/// <description>Description</description>
343+
/// </listheader>
344+
/// <item>
345+
/// <term>2</term>
346+
/// <description>Field deleted because the specified expiration time is due.</description>
347+
/// </item>
348+
/// <item>
349+
/// <term>1</term>
350+
/// <description>Expiration time set/updated.</description>
351+
/// </item>
352+
/// <item>
353+
/// <term>0</term>
354+
/// <description>Expiration time is not set/update (a specified ExpireWhen condition is not met).</description>
355+
/// </item>
356+
/// <item>
357+
/// <term>-1</term>
358+
/// <description>No such field exists.</description>
359+
/// </item>
360+
/// </list>
361+
/// </returns>
362+
ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
363+
364+
/// <summary>
365+
/// Set the time out on a field of the given set of fields of hash.
366+
/// After the timeout has expired, the field of the hash will automatically be deleted.
367+
/// </summary>
368+
/// <param name="key">The key of the hash.</param>
369+
/// <param name="hashFields">The fields in the hash to set expire time.</param>
370+
/// <param name="expiry">The exact date to expiry to set.</param>
371+
/// <param name="when">under which condition the expiration will be set using <see cref="ExpireWhen"/>.</param>
372+
/// <param name="flags">The flags to use for this operation.</param>
373+
/// <returns>
374+
/// Empty array if the key does not exist. Otherwise returns an array where each item is the result of operation for given fields:
375+
/// <list type="table">
376+
/// <listheader>
377+
/// <term>Result</term>
378+
/// <description>Description</description>
379+
/// </listheader>
380+
/// <item>
381+
/// <term>2</term>
382+
/// <description>Field deleted because the specified expiration time is due.</description>
383+
/// </item>
384+
/// <item>
385+
/// <term>1</term>
386+
/// <description>Expiration time set/updated.</description>
387+
/// </item>
388+
/// <item>
389+
/// <term>0</term>
390+
/// <description>Expiration time is not set/update (a specified ExpireWhen condition is not met).</description>
391+
/// </item>
392+
/// <item>
393+
/// <term>-1</term>
394+
/// <description>No such field exists.</description>
395+
/// </item>
396+
/// </list>
397+
/// </returns>
398+
ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
399+
400+
/// <summary>
401+
/// For each specified field, it gets the expiration time as a Unix timestamp in milliseconds (milliseconds since the Unix epoch).
402+
/// </summary>
403+
/// <param name="key">The key of the hash.</param>
404+
/// <param name="hashFields">The fields in the hash to get expire time.</param>
405+
/// <param name="flags">The flags to use for this operation.</param>
406+
/// <returns>
407+
/// Empty array if the key does not exist. Otherwise returns the result of operation for given fields:
408+
/// <list type="table">
409+
/// <listheader>
410+
/// <term>Result</term>
411+
/// <description>Description</description>
412+
/// </listheader>
413+
/// <item>
414+
/// <term>&gt; 0</term>
415+
/// <description>Expiration time, as a Unix timestamp in milliseconds.</description>
416+
/// </item>
417+
/// <item>
418+
/// <term>-1</term>
419+
/// <description>Field has no associated expiration time.</description>
420+
/// </item>
421+
/// <item>
422+
/// <term>-2</term>
423+
/// <description>No such field exists.</description>
424+
/// </item>
425+
/// </list>
426+
/// </returns>
427+
long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
428+
429+
/// <summary>
430+
/// For each specified field, it removes the expiration time.
431+
/// </summary>
432+
/// <param name="key">The key of the hash.</param>
433+
/// <param name="hashFields">The fields in the hash to remove expire time.</param>
434+
/// <param name="flags">The flags to use for this operation.</param>
435+
/// <returns>
436+
/// Empty array if the key does not exist. Otherwise returns the result of operation for given fields:
437+
/// <list type="table">
438+
/// <listheader>
439+
/// <term>Result</term>
440+
/// <description>Description</description>
441+
/// </listheader>
442+
/// <item>
443+
/// <term>1</term>
444+
/// <description>Expiration time was removed.</description>
445+
/// </item>
446+
/// <item>
447+
/// <term>-1</term>
448+
/// <description>Field has no associated expiration time.</description>
449+
/// </item>
450+
/// <item>
451+
/// <term>-2</term>
452+
/// <description>No such field exists.</description>
453+
/// </item>
454+
/// </list>
455+
/// </returns>
456+
PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
457+
458+
/// <summary>
459+
/// For each specified field, it gets the remaining time to live in milliseconds.
460+
/// </summary>
461+
/// <param name="key">The key of the hash.</param>
462+
/// <param name="hashFields">The fields in the hash to get expire time.</param>
463+
/// <param name="flags">The flags to use for this operation.</param>
464+
/// <returns>
465+
/// Empty array if the key does not exist. Otherwise returns the result of operation for given fields:
466+
/// <list type="table">
467+
/// <listheader>
468+
/// <term>Result</term>
469+
/// <description>Description</description>
470+
/// </listheader>
471+
/// <item>
472+
/// <term>&gt; 0</term>
473+
/// <description>Time to live, in milliseconds.</description>
474+
/// </item>
475+
/// <item>
476+
/// <term>-1</term>
477+
/// <description>Field has no associated expiration time.</description>
478+
/// </item>
479+
/// <item>
480+
/// <term>-2</term>
481+
/// <description>No such field exists.</description>
482+
/// </item>
483+
/// </list>
484+
/// </returns>
485+
long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
486+
328487
/// <summary>
329488
/// Returns the value associated with field in the hash stored at key.
330489
/// </summary>

src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs

+15
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,21 @@ public interface IDatabaseAsync : IRedisAsync
8484
/// <inheritdoc cref="IDatabase.HashExists(RedisKey, RedisValue, CommandFlags)"/>
8585
Task<bool> HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
8686

87+
/// <inheritdoc cref="IDatabase.HashFieldExpire(RedisKey, RedisValue[], TimeSpan, ExpireWhen, CommandFlags)"/>
88+
Task<ExpireResult[]> HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
89+
90+
/// <inheritdoc cref="IDatabase.HashFieldExpire(RedisKey, RedisValue[], DateTime, ExpireWhen, CommandFlags)"/>
91+
Task<ExpireResult[]> HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
92+
93+
/// <inheritdoc cref="IDatabase.HashFieldGetExpireDateTime(RedisKey, RedisValue[], CommandFlags)"/>
94+
Task<long[]> HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
95+
96+
/// <inheritdoc cref="HashFieldPersistAsync(RedisKey, RedisValue[], CommandFlags)"/>
97+
Task<PersistResult[]> HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
98+
99+
/// <inheritdoc cref="IDatabase.HashFieldGetTimeToLive(RedisKey, RedisValue[], CommandFlags)"/>
100+
Task<long[]> HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None);
101+
87102
/// <inheritdoc cref="IDatabase.HashGet(RedisKey, RedisValue, CommandFlags)"/>
88103
Task<RedisValue> HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None);
89104

src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs

+15
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,21 @@ public Task<bool> HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFla
8484
public Task<bool> HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) =>
8585
Inner.HashExistsAsync(ToInner(key), hashField, flags);
8686

87+
public Task<ExpireResult[]> HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
88+
Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags);
89+
90+
public Task<ExpireResult[]> HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
91+
Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags);
92+
93+
public Task<long[]> HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
94+
Inner.HashFieldGetExpireDateTimeAsync(ToInner(key), hashFields, flags);
95+
96+
public Task<PersistResult[]> HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
97+
Inner.HashFieldPersistAsync(ToInner(key), hashFields, flags);
98+
99+
public Task<long[]> HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
100+
Inner.HashFieldGetTimeToLiveAsync(ToInner(key), hashFields, flags);
101+
87102
public Task<HashEntry[]> HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
88103
Inner.HashGetAllAsync(ToInner(key), flags);
89104

src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs

+15
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,21 @@ public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags =
8181
public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) =>
8282
Inner.HashExists(ToInner(key), hashField, flags);
8383

84+
public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
85+
Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags);
86+
87+
public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) =>
88+
Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags);
89+
90+
public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
91+
Inner.HashFieldGetExpireDateTime(ToInner(key), hashFields, flags);
92+
93+
public PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
94+
Inner.HashFieldPersist(ToInner(key), hashFields, flags);
95+
96+
public long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags) =>
97+
Inner.HashFieldGetTimeToLive(ToInner(key), hashFields, flags);
98+
8499
public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) =>
85100
Inner.HashGetAll(ToInner(key), flags);
86101

0 commit comments

Comments
 (0)