Skip to content

Commit bcc647b

Browse files
Support EXPIRETIME and PEXPIRETIME (#2083)
Adds support for https://redis.io/commands/expiretime/ https://redis.io/commands/pexpiretime/ (#2055) Co-authored-by: Nick Craver <[email protected]>
1 parent 8dc6456 commit bcc647b

14 files changed

+410
-38
lines changed

docs/ReleaseNotes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
- Adds: Support for `SINTERCARD` with `.SetIntersectionLength()`/`.SetIntersectionLengthAsync()` ([#2078 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2078))
1616
- Adds: Support for `LPOS` with `.ListPosition()`/`.ListPositionAsync()` and `.ListPositions()`/`.ListPositionsAsync()` ([#2080 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2080))
1717
- Adds: Support for `ZMSCORE` with `.SortedSetScores()`/.`SortedSetScoresAsync()` ([#2082 by ttingen](https://github.com/StackExchange/StackExchange.Redis/pull/2082))
18+
- Adds: Support for `NX | XX | GT | LT` to `EXPIRE`, `EXPIREAT`, `PEXPIRE`, and `PEXPIREAT` with `.KeyExpire()`/`.KeyExpireAsync()` ([#2083 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2083))
19+
- Adds: Support for `EXPIRETIME`, and `PEXPIRETIME` with `.KeyExpireTime()`/`.KeyExpireTimeAsync()` ([#2083 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2083))
1820
- Fix: For streams, properly hash `XACK`, `XCLAIM`, and `XPENDING` in cluster scenarios to eliminate `MOVED` retries ([#2085 by nielsderdaele](https://github.com/StackExchange/StackExchange.Redis/pull/2085))
1921
- Adds: Support for `OBJECT REFCOUNT` with `.KeyRefCount()`/`.KeyRefCountAsync()` ([#2087 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2087))
2022
- Adds: Support for `OBJECT ENCODING` with `.KeyEncoding()`/`.KeyEncodingAsync()` ([#2088 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2088))
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
3+
namespace StackExchange.Redis;
4+
5+
/// <summary>
6+
/// Specifies when to set the expiry for a key.
7+
/// </summary>
8+
public enum ExpireWhen
9+
{
10+
/// <summary>
11+
/// Set expiry whether or not there is an existing expiry.
12+
/// </summary>
13+
Always,
14+
/// <summary>
15+
/// Set expiry only when the new expiry is greater than current one.
16+
/// </summary>
17+
GreaterThanCurrentExpiry,
18+
/// <summary>
19+
/// Set expiry only when the key has an existing expiry.
20+
/// </summary>
21+
HasExpiry,
22+
/// <summary>
23+
/// Set expiry only when the key has no expiry.
24+
/// </summary>
25+
HasNoExpiry,
26+
/// <summary>
27+
/// Set expiry only when the new expiry is less than current one
28+
/// </summary>
29+
LessThanCurrentExpiry,
30+
}
31+
32+
internal static class ExpiryOptionExtensions
33+
{
34+
public static RedisValue ToLiteral(this ExpireWhen op) => op switch
35+
{
36+
ExpireWhen.HasNoExpiry => RedisLiterals.NX,
37+
ExpireWhen.HasExpiry => RedisLiterals.XX,
38+
ExpireWhen.GreaterThanCurrentExpiry => RedisLiterals.GT,
39+
ExpireWhen.LessThanCurrentExpiry => RedisLiterals.LT,
40+
_ => throw new ArgumentOutOfRangeException(nameof(op))
41+
};
42+
}

src/StackExchange.Redis/Enums/RedisCommand.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ internal enum RedisCommand
3737
EXISTS,
3838
EXPIRE,
3939
EXPIREAT,
40+
EXPIRETIME,
4041

4142
FLUSHALL,
4243
FLUSHDB,
@@ -110,6 +111,7 @@ internal enum RedisCommand
110111
PERSIST,
111112
PEXPIRE,
112113
PEXPIREAT,
114+
PEXPIRETIME,
113115
PFADD,
114116
PFCOUNT,
115117
PFMERGE,

src/StackExchange.Redis/Interfaces/IDatabase.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,8 @@ public interface IDatabase : IRedis, IDatabaseAsync
641641
long KeyExists(RedisKey[] keys, CommandFlags flags = CommandFlags.None);
642642

643643
/// <summary>
644-
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.
644+
/// Set a timeout on <paramref name="key"/>.
645+
/// After the timeout has expired, the key will automatically be deleted.
645646
/// A key with an associated timeout is said to be volatile in Redis terminology.
646647
/// </summary>
647648
/// <param name="key">The key to set the expiration for.</param>
@@ -665,7 +666,22 @@ public interface IDatabase : IRedis, IDatabaseAsync
665666
bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None);
666667

667668
/// <summary>
668-
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.
669+
/// Set a timeout on <paramref name="key"/>.
670+
/// After the timeout has expired, the key will automatically be deleted.
671+
/// A key with an associated timeout is said to be volatile in Redis terminology.
672+
/// </summary>
673+
/// <param name="key">The key to set the expiration for.</param>
674+
/// <param name="expiry">The timeout to set.</param>
675+
/// <param name="when">Since Redis 7.0.0, you can choose under which condition the expiration will be set using <see cref="ExpireWhen"/>.</param>
676+
/// <param name="flags">The flags to use for this operation.</param>
677+
/// <returns><see langword="true"/> if the timeout was set. <see langword="false"/> if key does not exist or the timeout could not be set.</returns>
678+
/// <remarks>https://redis.io/commands/expire</remarks>
679+
/// <remarks>https://redis.io/commands/pexpire</remarks>
680+
bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None);
681+
682+
/// <summary>
683+
/// Set a timeout on <paramref name="key"/>.
684+
/// After the timeout has expired, the key will automatically be deleted.
669685
/// A key with an associated timeout is said to be volatile in Redis terminology.
670686
/// </summary>
671687
/// <param name="key">The key to set the expiration for.</param>
@@ -688,6 +704,30 @@ public interface IDatabase : IRedis, IDatabaseAsync
688704
/// <remarks>https://redis.io/commands/persist</remarks>
689705
bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None);
690706

707+
/// <summary>
708+
/// Set a timeout on <paramref name="key"/>.
709+
/// After the timeout has expired, the key will automatically be deleted.
710+
/// A key with an associated timeout is said to be volatile in Redis terminology.
711+
/// </summary>
712+
/// <param name="key">The key to set the expiration for.</param>
713+
/// <param name="expiry">The timeout to set.</param>
714+
/// <param name="when">Since Redis 7.0.0, you can choose under which condition the expiration will be set using <see cref="ExpireWhen"/>.</param>
715+
/// <param name="flags">The flags to use for this operation.</param>
716+
/// <returns><see langword="true"/> if the timeout was set. <see langword="false"/> if key does not exist or the timeout could not be set.</returns>
717+
/// <remarks>https://redis.io/commands/expire</remarks>
718+
/// <remarks>https://redis.io/commands/pexpire</remarks>
719+
bool KeyExpire(RedisKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None);
720+
721+
/// <summary>
722+
/// Returns the absolute time at which the given <paramref name="key"/> will expire, if it exists and has an expiration.
723+
/// </summary>
724+
/// <param name="key">The key to get the expiration for.</param>
725+
/// <param name="flags">The flags to use for this operation.</param>
726+
/// <returns>The time at which the given key will expire, or <see langword="null"/> if the key does not exist or has no associated expiration time.</returns>
727+
/// <remarks>https://redis.io/commands/expiretime</remarks>
728+
/// <remarks>https://redis.io/commands/pexpiretime</remarks>
729+
DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None);
730+
691731
/// <summary>
692732
/// Returns the time since the object stored at the specified key is idle (not requested by read or write operations).
693733
/// </summary>

src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,8 @@ public interface IDatabaseAsync : IRedisAsync
617617
Task<long> KeyExistsAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None);
618618

619619
/// <summary>
620-
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.
620+
/// Set a timeout on <paramref name="key"/>.
621+
/// After the timeout has expired, the key will automatically be deleted.
621622
/// A key with an associated timeout is said to be volatile in Redis terminology.
622623
/// </summary>
623624
/// <param name="key">The key to set the expiration for.</param>
@@ -632,7 +633,8 @@ public interface IDatabaseAsync : IRedisAsync
632633
/// </para>
633634
/// <para>
634635
/// Since Redis 2.1.3, you can update the timeout of a key.
635-
/// It is also possible to remove the timeout using the PERSIST command. See the page on key expiry for more information.
636+
/// It is also possible to remove the timeout using the PERSIST command.
637+
/// See the page on key expiry for more information.
636638
/// </para>
637639
/// </remarks>
638640
/// <remarks>https://redis.io/commands/expire</remarks>
@@ -641,7 +643,22 @@ public interface IDatabaseAsync : IRedisAsync
641643
Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None);
642644

643645
/// <summary>
644-
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.
646+
/// Set a timeout on <paramref name="key"/>.
647+
/// After the timeout has expired, the key will automatically be deleted.
648+
/// A key with an associated timeout is said to be volatile in Redis terminology.
649+
/// </summary>
650+
/// <param name="key">The key to set the expiration for.</param>
651+
/// <param name="expiry">The timeout to set.</param>
652+
/// <param name="when">Since Redis 7.0.0, you can choose under which condition the expiration will be set using <see cref="ExpireWhen"/>.</param>
653+
/// <param name="flags">The flags to use for this operation.</param>
654+
/// <returns><see langword="true"/> if the timeout was set. <see langword="false"/> if key does not exist or the timeout could not be set.</returns>
655+
/// <remarks>https://redis.io/commands/expire</remarks>
656+
/// <remarks>https://redis.io/commands/pexpire</remarks>
657+
Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None);
658+
659+
/// <summary>
660+
/// Set a timeout on <paramref name="key"/>.
661+
/// After the timeout has expired, the key will automatically be deleted.
645662
/// A key with an associated timeout is said to be volatile in Redis terminology.
646663
/// </summary>
647664
/// <param name="key">The key to set the expiration for.</param>
@@ -656,14 +673,39 @@ public interface IDatabaseAsync : IRedisAsync
656673
/// </para>
657674
/// <para>
658675
/// Since Redis 2.1.3, you can update the timeout of a key.
659-
/// It is also possible to remove the timeout using the PERSIST command. See the page on key expiry for more information.
676+
/// It is also possible to remove the timeout using the PERSIST command.
677+
/// See the page on key expiry for more information.
660678
/// </para>
661679
/// </remarks>
662680
/// <remarks>https://redis.io/commands/expireat</remarks>
663681
/// <remarks>https://redis.io/commands/pexpireat</remarks>
664682
/// <remarks>https://redis.io/commands/persist</remarks>
665683
Task<bool> KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None);
666684

685+
/// <summary>
686+
/// Set a timeout on <paramref name="key"/>.
687+
/// After the timeout has expired, the key will automatically be deleted.
688+
/// A key with an associated timeout is said to be volatile in Redis terminology.
689+
/// </summary>
690+
/// <param name="key">The key to set the expiration for.</param>
691+
/// <param name="expiry">The timeout to set.</param>
692+
/// <param name="when">Since Redis 7.0.0, you can choose under which condition the expiration will be set using <see cref="ExpireWhen"/>.</param>
693+
/// <param name="flags">The flags to use for this operation.</param>
694+
/// <returns><see langword="true"/> if the timeout was set. <see langword="false"/> if key does not exist or the timeout could not be set.</returns>
695+
/// <remarks>https://redis.io/commands/expire</remarks>
696+
/// <remarks>https://redis.io/commands/pexpire</remarks>
697+
Task<bool> KeyExpireAsync(RedisKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None);
698+
699+
/// <summary>
700+
/// Returns the absolute time at which the given <paramref name="key"/> will expire, if it exists and has an expiration.
701+
/// </summary>
702+
/// <param name="key">The key to get the expiration for.</param>
703+
/// <param name="flags">The flags to use for this operation.</param>
704+
/// <returns>The time at which the given key will expire, or <see langword="null"/> if the key does not exist or has no associated expiration time.</returns>
705+
/// <remarks>https://redis.io/commands/expiretime</remarks>
706+
/// <remarks>https://redis.io/commands/pexpiretime</remarks>
707+
Task<DateTime?> KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None);
708+
667709
/// <summary>
668710
/// Returns the time since the object stored at the specified key is idle (not requested by read or write operations).
669711
/// </summary>

src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,18 @@ public long KeyExists(RedisKey[] keys, CommandFlags flags = CommandFlags.None) =
170170
public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) =>
171171
Inner.KeyExpire(ToInner(key), expiry, flags);
172172

173+
public bool KeyExpire(RedisKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) =>
174+
Inner.KeyExpire(ToInner(key), expiry, when, flags);
175+
173176
public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) =>
174177
Inner.KeyExpire(ToInner(key), expiry, flags);
175178

179+
public bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) =>
180+
Inner.KeyExpire(ToInner(key), expiry, when, flags);
181+
182+
public DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None) =>
183+
Inner.KeyExpireTime(ToInner(key), flags);
184+
176185
public TimeSpan? KeyIdleTime(RedisKey key, CommandFlags flags = CommandFlags.None) =>
177186
Inner.KeyIdleTime(ToInner(key), flags);
178187

src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,18 @@ public Task<long> KeyExistsAsync(RedisKey[] keys, CommandFlags flags = CommandFl
181181
public Task<bool> KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) =>
182182
Inner.KeyExpireAsync(ToInner(key), expiry, flags);
183183

184+
public Task<bool> KeyExpireAsync(RedisKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) =>
185+
Inner.KeyExpireAsync(ToInner(key), expiry, when, flags);
186+
184187
public Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) =>
185188
Inner.KeyExpireAsync(ToInner(key), expiry, flags);
186189

190+
public Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) =>
191+
Inner.KeyExpireAsync(ToInner(key), expiry, when, flags);
192+
193+
public Task<DateTime?> KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
194+
Inner.KeyExpireTimeAsync(ToInner(key), flags);
195+
187196
public Task<TimeSpan?> KeyIdleTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
188197
Inner.KeyIdleTimeAsync(ToInner(key), flags);
189198

0 commit comments

Comments
 (0)