Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions docs/CommandCoverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Statuses: `policy` (excluded by design), `not-applicable` (not meaningful for a
|---|---|---|---|
| `HTTL` | 7.4.0 | `not-applicable` | Second-precision twin of `HPTTL`; the library always reads `HPTTL`. |
| `STTL` | 8.12.0 | `not-applicable` | Second-precision twin of `SPTTL`, as `HTTL`. |
| `SEXPIRETIME` | 8.12.0 | `not-applicable` | Second-precision twin of `SPEXPIRETIME`, as `HTTL`; the library always reads `SPEXPIRETIME`. |
| `RESET` | 6.2.0 | `not-applicable` | Discards the per-connection state the multiplexer negotiated (`HELLO`, `CLIENT SETNAME`, `SELECT`, subscriptions). Dispose and reconnect instead. |
| `CLIENT REPLY` | 3.2.0 | `not-applicable` | Suppressing replies breaks the request/response pairing the multiplexer relies on; `CommandFlags.FireAndForget` is the client-side equivalent. |
| `CLIENT UNBLOCK` | 5.0.0 | `not-applicable` | Unblocks a client parked in a blocking command, which this library does not issue. |
Expand Down Expand Up @@ -162,15 +163,6 @@ PRs welcome; follow the `implement-resp-command` skill. Pre-release server featu
| `BLESS GET` | 8.12.0 | `todo` | As `BLESS`. |
| `BLESS CLEAR` | 8.12.0 | `todo` | As `BLESS`. |
| `BLESS SCAN` | 8.12.0 | `todo` | As `BLESS`. |
| `SADDEX` | 8.12.0 | `todo` | Set member expiry; mirror the hash-field expiry surface (`HashFieldExpire` et al). |
| `SEXPIRE` | 8.12.0 | `todo` | Set member expiry. |
| `SEXPIREAT` | 8.12.0 | `todo` | Set member expiry. |
| `SEXPIRETIME` | 8.12.0 | `todo` | Set member expiry (mirrors `HEXPIRETIME`). |
| `SPERSIST` | 8.12.0 | `todo` | Set member expiry. |
| `SPEXPIRE` | 8.12.0 | `todo` | Set member expiry. |
| `SPEXPIREAT` | 8.12.0 | `todo` | Set member expiry. |
| `SPEXPIRETIME` | 8.12.0 | `todo` | Set member expiry. |
| `SPTTL` | 8.12.0 | `todo` | Set member expiry; the read path (see `STTL` above). |
| `MEMORY USAGE` | 4.0.0 | `todo` | Per-key memory; `MEMORY DOCTOR`/`STATS`/`PURGE` are exposed, this one is the useful one for profiling. |
| `CLUSTER INFO` | 3.0.0 | `todo` | Cluster state summary (`cluster_state`, slot counts, epoch) on `IServer`, next to `ClusterNodes`. |
| `LATENCY HISTOGRAM` | 7.0.0 | `todo` | Per-command latency histograms on `IServer`, next to `LATENCY LATEST`/`HISTORY`. |
Expand Down
4 changes: 4 additions & 0 deletions docs/exp/SER011.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ New features in Redis 8.12:

- Bit enumeration (`BITRANGEINTARRAY`), surfaced as `IDatabase.StringBitPositions` / `StringBitPositionsAsync`:
the positions of the set (or clear) bits in a byte or bit range of a string or native bitmap, with optional paging
- Per-member set expiration (`SADDEX`, `SEXPIRE`/`SPEXPIRE`/`SEXPIREAT`/`SPEXPIREAT`, `SPTTL`,
`SPEXPIRETIME`, `SPERSIST`), surfaced as `IDatabase.SetAdd` (the `Expiration` overloads), `SetMemberExpire`,
`SetMemberGetTimeToLive`, `SetMemberGetExpireDateTime` and `SetMemberPersist` (and async
equivalents)

The corresponding library features must also be considered subject to change:

Expand Down
301 changes: 301 additions & 0 deletions notes/set-member-expiry/plan.md

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/StackExchange.Redis/Enums/RedisCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ internal enum RedisCommand
RPUSHX,

SADD,
SADDEX,
SAVE,
SCAN,
SCARD,
Expand All @@ -208,6 +209,8 @@ internal enum RedisCommand
SETEX,
SETNX,
SETRANGE,
SEXPIRE,
SEXPIREAT,
SHUTDOWN,
SINTER,
SINTERCARD,
Expand All @@ -221,7 +224,12 @@ internal enum RedisCommand
SORT,
[AsciiHash("SORT_RO")]
SORT_RO,
SPERSIST,
SPEXPIRE,
SPEXPIREAT,
SPEXPIRETIME,
SPOP,
SPTTL,
SPUBLISH,
SRANDMEMBER,
SREM,
Expand Down Expand Up @@ -417,14 +425,20 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
case RedisCommand.RPUSH:
case RedisCommand.RPUSHX:
case RedisCommand.SADD:
case RedisCommand.SADDEX:
case RedisCommand.SDIFFSTORE:
case RedisCommand.SET:
case RedisCommand.SETBIT:
case RedisCommand.SETEX:
case RedisCommand.SETNX:
case RedisCommand.SETRANGE:
case RedisCommand.SEXPIRE:
case RedisCommand.SEXPIREAT:
case RedisCommand.SINTERSTORE:
case RedisCommand.SMOVE:
case RedisCommand.SPERSIST:
case RedisCommand.SPEXPIRE:
case RedisCommand.SPEXPIREAT:
case RedisCommand.SPOP:
case RedisCommand.SREM:
case RedisCommand.SUNIONSTORE:
Expand Down Expand Up @@ -556,6 +570,8 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
case RedisCommand.SMEMBERS:
case RedisCommand.SMISMEMBER:
case RedisCommand.SORT_RO:
case RedisCommand.SPEXPIRETIME:
case RedisCommand.SPTTL:
case RedisCommand.SPUBLISH:
case RedisCommand.SRANDMEMBER:
case RedisCommand.SSUBSCRIBE:
Expand Down
191 changes: 191 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,48 @@ public partial interface IDatabase : IRedis, IDatabaseAsync
/// <remarks><seealso href="https://redis.io/commands/sadd"/></remarks>
long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Adds the specified member to the set stored at key and/or sets its per-member expiration, in one atomic call
/// (<c>SADDEX</c>). If key does not exist, a new set is created first.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="value">The value to add to the set and/or set the expiration of.</param>
/// <param name="expiry">The expiry to set on the member: a <see cref="TimeSpan"/> or <see cref="DateTime"/> (implicitly converted),
/// or <see cref="Expiration.KeepTtl"/> to keep an existing expiration (a new member gets none). The default (none) sends the bare
/// form, which <b>discards</b> any expiration the member already has - this is <c>SADDEX</c>'s behaviour, not <c>SADD</c>'s, which
/// leaves existing members (and their expirations) untouched. <see cref="Expiration.Persist"/> is not supported here: use
/// <see cref="SetMemberPersist"/> to remove expirations.</param>
/// <param name="when">Which condition to apply the operation under; see the remarks.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns><see langword="true"/> if at least one member was added, updated or deleted, else <see langword="false"/>
/// (nothing changed, or the <paramref name="when"/> condition was not met).</returns>
/// <remarks>
/// <para>The <paramref name="when"/> condition applies to the member (<see cref="When.NotExists"/> sends <c>MNX</c>: only if the member
/// does not exist yet; <see cref="When.Exists"/> sends <c>MXX</c>: only if it already exists); with multiple members the condition is
/// <b>all-or-nothing across the batch</b> - one member that breaks it vetoes the whole call, and nothing is changed.</para>
/// <para>An expiration that is already in the past deletes the member instead of adding it.</para>
/// <para><seealso href="https://redis.io/commands/saddex"/></para>
/// </remarks>
[Experimental(Experiments.Server_8_12, UrlFormat = Experiments.UrlFormat)]
#pragma warning disable RS0026 // similar overloads
bool SetAdd(RedisKey key, RedisValue value, Expiration expiry, When when = When.Always, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Adds the specified members to the set stored at key and/or sets their per-member expiration, in one atomic call
/// (<c>SADDEX</c>). If key does not exist, a new set is created first.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="values">The values to add to the set and/or set the expiration of.</param>
/// <param name="expiry"><inheritdoc cref="SetAdd(RedisKey, RedisValue, Expiration, When, CommandFlags)" path="/param[@name='expiry']"/></param>
/// <param name="when"><inheritdoc cref="SetAdd(RedisKey, RedisValue, Expiration, When, CommandFlags)" path="/param[@name='when']"/></param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns><inheritdoc cref="SetAdd(RedisKey, RedisValue, Expiration, When, CommandFlags)" path="/returns"/>
/// Unlike <see cref="SetAdd(RedisKey, RedisValue[], CommandFlags)"/>, the server does not report how many members were added.</returns>
/// <remarks><inheritdoc cref="SetAdd(RedisKey, RedisValue, Expiration, When, CommandFlags)" path="/remarks"/></remarks>
[Experimental(Experiments.Server_8_12, UrlFormat = Experiments.UrlFormat)]
bool SetAdd(RedisKey key, RedisValue[] values, Expiration expiry, When when = When.Always, CommandFlags flags = CommandFlags.None);
#pragma warning restore RS0026

/// <summary>
/// Returns the members of the set resulting from the specified operation against the given sets.
/// </summary>
Expand Down Expand Up @@ -1915,6 +1957,155 @@ public partial interface IDatabase : IRedis, IDatabaseAsync
/// <remarks><seealso href="https://redis.io/commands/scard"/></remarks>
long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Sets a per-member time to live on the given members of the set stored at key.
/// After the timeout has expired, the member is automatically removed from the set.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="members">The members to set the expiration of.</param>
/// <param name="expiry">The timeout to set.</param>
/// <param name="when">Under which condition the expiration will be set, per member, using <see cref="ExpireWhen"/>.
/// A member with no expiration counts as infinitely far away for <see cref="ExpireWhen.GreaterThanCurrentExpiry"/>.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// An array with, per member and in order, the result of the operation:
/// <list type="table">
/// <listheader>
/// <term>Result</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>2</term>
/// <description>Member 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 not set/updated (a specified ExpireWhen condition is not met).</description>
/// </item>
/// <item>
/// <term>-2</term>
/// <description>No such member exists.</description>
/// </item>
/// </list>
/// </returns>
/// <remarks><seealso href="https://redis.io/commands/sexpire"/></remarks>
[Experimental(Experiments.Server_8_12, UrlFormat = Experiments.UrlFormat)]
#pragma warning disable RS0026 // similar overloads
ExpireResult[] SetMemberExpire(RedisKey key, RedisValue[] members, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Sets a per-member expiration time on the given members of the set stored at key.
/// After the timeout has expired, the member is automatically removed from the set.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="members">The members to set the expiration of.</param>
/// <param name="expiry">The exact expiry time to set.</param>
/// <param name="when"><inheritdoc cref="SetMemberExpire(RedisKey, RedisValue[], TimeSpan, ExpireWhen, CommandFlags)" path="/param[@name='when']"/></param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns><inheritdoc cref="SetMemberExpire(RedisKey, RedisValue[], TimeSpan, ExpireWhen, CommandFlags)" path="/returns"/></returns>
/// <remarks><seealso href="https://redis.io/commands/sexpireat"/></remarks>
[Experimental(Experiments.Server_8_12, UrlFormat = Experiments.UrlFormat)]
ExpireResult[] SetMemberExpire(RedisKey key, RedisValue[] members, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
#pragma warning restore RS0026

/// <summary>
/// For each specified member of the set stored at key, gets the expiration time as a Unix timestamp in milliseconds (milliseconds since the Unix epoch).
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="members">The members to get the expiration time of.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// An array with, per member and in order, the result of the operation:
/// <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>Member has no associated expiration time.</description>
/// </item>
/// <item>
/// <term>-2</term>
/// <description>No such member exists.</description>
/// </item>
/// </list>
/// </returns>
/// <remarks><seealso href="https://redis.io/commands/spexpiretime"/></remarks>
[Experimental(Experiments.Server_8_12, UrlFormat = Experiments.UrlFormat)]
long[] SetMemberGetExpireDateTime(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None);

/// <summary>
/// For each specified member of the set stored at key, gets the remaining time to live in milliseconds.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="members">The members to get the time to live of.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// An array with, per member and in order, the result of the operation:
/// <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>Member has no associated expiration time.</description>
/// </item>
/// <item>
/// <term>-2</term>
/// <description>No such member exists.</description>
/// </item>
/// </list>
/// </returns>
/// <remarks><seealso href="https://redis.io/commands/spttl"/></remarks>
[Experimental(Experiments.Server_8_12, UrlFormat = Experiments.UrlFormat)]
long[] SetMemberGetTimeToLive(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None);

/// <summary>
/// For each specified member of the set stored at key, removes the expiration time.
/// A member without an expiration stays in the set until removed explicitly.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="members">The members to remove the expiration of.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// An array with, per member and in order, the result of the operation:
/// <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>Member has no associated expiration time.</description>
/// </item>
/// <item>
/// <term>-2</term>
/// <description>No such member exists.</description>
/// </item>
/// </list>
/// </returns>
/// <remarks><seealso href="https://redis.io/commands/spersist"/></remarks>
[Experimental(Experiments.Server_8_12, UrlFormat = Experiments.UrlFormat)]
PersistResult[] SetMemberPersist(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns all the members of the set value stored at key.
/// </summary>
Expand Down
Loading
Loading