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
1 change: 1 addition & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Current package versions:

- Support Redis 8.4 CAS/CAD operations (`DIGEST`, and the `IFEQ`, `IFNE`, `IFDEQ`, `IFDNE` modifiers on `SET` / `DEL`)
via the new `ValueCondition` abstraction, and use CAS/CAD operations for `Lock*` APIs when possible ([#2978 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2978))
- **note**: overload resolution for `StringSet[Async]` may be impacted in niche cases, requiring trivial build changes (there are no runtime-breaking changes such as missing methods)
- Support `XREADGROUP CLAIM` ([#2972 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2972))
- Support `MSETEX` (Redis 8.4.0) for multi-key operations with expiration ([#2977 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2977))

Expand Down
2 changes: 1 addition & 1 deletion src/StackExchange.Redis/Expiration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ private static void ThrowMode(ExpirationMode mode) =>
/// <inheritdoc/>
public override bool Equals(object? obj) => obj is Expiration other && _valueAndMode == other._valueAndMode;

internal int Tokens => Mode switch
internal int TokenCount => Mode switch
{
ExpirationMode.Default or ExpirationMode.NotUsed => 0,
ExpirationMode.KeepTtl or ExpirationMode.Persist => 1,
Expand Down
8 changes: 3 additions & 5 deletions src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3182,7 +3182,6 @@ IEnumerable<SortedSetEntry> SortedSetScan(
/// <param name="when">The condition to enforce.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <remarks>See <seealso href="https://redis.io/commands/delex"/>.</remarks>
[Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
bool StringDelete(RedisKey key, ValueCondition when, CommandFlags flags = CommandFlags.None);

/// <summary>
Expand Down Expand Up @@ -3411,7 +3410,7 @@ IEnumerable<SortedSetEntry> SortedSetScan(
/// <param name="flags">The flags to use for this operation.</param>
/// <returns><see langword="true"/> if the string was set, <see langword="false"/> otherwise.</returns>
/// <remarks><seealso href="https://redis.io/commands/set"/></remarks>
bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None);
bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, bool keepTtl, When when = When.Always, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Set <paramref name="key"/> to hold the string <paramref name="value"/>, if it matches the given <paramref name="when"/> condition.
Expand All @@ -3422,9 +3421,8 @@ IEnumerable<SortedSetEntry> SortedSetScan(
/// <param name="when">The condition to enforce.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <remarks>See <seealso href="https://redis.io/commands/delex"/>.</remarks>
[Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
#pragma warning disable RS0027
bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, ValueCondition when, CommandFlags flags = CommandFlags.None);
#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads
bool StringSet(RedisKey key, RedisValue value, Expiration expiry = default, ValueCondition when = default, CommandFlags flags = CommandFlags.None);
#pragma warning restore RS0027

/// <summary>
Expand Down
8 changes: 3 additions & 5 deletions src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,6 @@ IAsyncEnumerable<SortedSetEntry> SortedSetScanAsync(
Task<long> StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.StringDelete(RedisKey, ValueCondition, CommandFlags)"/>
[Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
Task<bool> StringDeleteAsync(RedisKey key, ValueCondition when, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.StringDecrement(RedisKey, double, CommandFlags)"/>
Expand Down Expand Up @@ -840,12 +839,11 @@ IAsyncEnumerable<SortedSetEntry> SortedSetScanAsync(
Task<bool> StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags);

/// <inheritdoc cref="IDatabase.StringSet(RedisKey, RedisValue, TimeSpan?, bool, When, CommandFlags)"/>
Task<bool> StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None);
Task<bool> StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, bool keepTtl, When when = When.Always, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.StringSet(RedisKey, RedisValue, TimeSpan?, ValueCondition, CommandFlags)"/>
[Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
/// <inheritdoc cref="IDatabase.StringSet(RedisKey, RedisValue, Expiration, ValueCondition, CommandFlags)"/>
#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads
Task<bool> StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, ValueCondition when, CommandFlags flags = CommandFlags.None);
Task<bool> StringSetAsync(RedisKey key, RedisValue value, Expiration expiry = default, ValueCondition when = default, CommandFlags flags = CommandFlags.None);
#pragma warning restore RS0027

/// <inheritdoc cref="IDatabase.StringSet(KeyValuePair{RedisKey, RedisValue}[], When, CommandFlags)"/>
Expand Down
2 changes: 1 addition & 1 deletion src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ public Task<long> StringIncrementAsync(RedisKey key, long value = 1, CommandFlag
public Task<long> StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
Inner.StringLengthAsync(ToInner(key), flags);

public Task<bool> StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, ValueCondition when, CommandFlags flags = CommandFlags.None)
public Task<bool> StringSetAsync(RedisKey key, RedisValue value, Expiration expiry, ValueCondition when, CommandFlags flags = CommandFlags.None)
=> Inner.StringSetAsync(ToInner(key), value, expiry, when, flags);

public Task<bool> StringSetAsync(KeyValuePair<RedisKey, RedisValue>[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@ public long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = C
public long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None) =>
Inner.StringLength(ToInner(key), flags);

public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, ValueCondition when, CommandFlags flags = CommandFlags.None)
public bool StringSet(RedisKey key, RedisValue value, Expiration expiry, ValueCondition when, CommandFlags flags = CommandFlags.None)
=> Inner.StringSet(ToInner(key), value, expiry, when, flags);

public bool StringSet(KeyValuePair<RedisKey, RedisValue>[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
Expand Down
23 changes: 5 additions & 18 deletions src/StackExchange.Redis/Message.ValueCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal partial class Message
public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in ValueCondition when)
=> new KeyConditionMessage(db, flags, command, key, when);

public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value, TimeSpan? expiry, in ValueCondition when)
public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value, Expiration expiry, in ValueCondition when)
=> new KeyValueExpiryConditionMessage(db, flags, command, key, value, expiry, when);

private sealed class KeyConditionMessage(
Expand Down Expand Up @@ -36,35 +36,22 @@ private sealed class KeyValueExpiryConditionMessage(
RedisCommand command,
in RedisKey key,
in RedisValue value,
TimeSpan? expiry,
Expiration expiry,
in ValueCondition when)
: CommandKeyBase(db, flags, command, key)
{
private readonly RedisValue _value = value;
private readonly ValueCondition _when = when;
private readonly TimeSpan? _expiry = expiry == TimeSpan.MaxValue ? null : expiry;
private readonly Expiration _expiry = expiry;

public override int ArgCount => 2 + _when.TokenCount + (_expiry is null ? 0 : 2);
public override int ArgCount => 2 + _expiry.TokenCount + _when.TokenCount;

protected override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, ArgCount);
physical.Write(Key);
physical.WriteBulkString(_value);
if (_expiry.HasValue)
{
var ms = (long)_expiry.GetValueOrDefault().TotalMilliseconds;
if ((ms % 1000) == 0)
{
physical.WriteBulkString("EX"u8);
physical.WriteBulkString(ms / 1000);
}
else
{
physical.WriteBulkString("PX"u8);
physical.WriteBulkString(ms);
}
}
_expiry.WriteTo(physical);
_when.WriteTo(physical);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/StackExchange.Redis/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1711,7 +1711,7 @@ public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
// - MSETNX {key1} {value1} [{key2} {value2}...]
// - MSETEX {count} {key1} {value1} [{key2} {value2}...] [standard-expiry-tokens]
public override int ArgCount => Command == RedisCommand.MSETEX
? (1 + (2 * values.Length) + expiry.Tokens + (when is When.Exists or When.NotExists ? 1 : 0))
? (1 + (2 * values.Length) + expiry.TokenCount + (when is When.Exists or When.NotExists ? 1 : 0))
: (2 * values.Length); // MSET/MSETNX only support simple syntax

protected override void WriteImpl(PhysicalConnection physical)
Expand Down
31 changes: 29 additions & 2 deletions src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,6 @@ StackExchange.Redis.IDatabase.StringLength(StackExchange.Redis.RedisKey key, Sta
StackExchange.Redis.IDatabase.StringLongestCommonSubsequence(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string?
StackExchange.Redis.IDatabase.StringLongestCommonSubsequenceLength(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.StringLongestCommonSubsequenceWithMatches(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, long minLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.LCSMatchResult
StackExchange.Redis.IDatabase.StringSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
StackExchange.Redis.IDatabase.StringSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when) -> bool
StackExchange.Redis.IDatabase.StringSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> bool
StackExchange.Redis.IDatabase.StringSet(System.Collections.Generic.KeyValuePair<StackExchange.Redis.RedisKey, StackExchange.Redis.RedisValue>[]! values, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> bool
Expand Down Expand Up @@ -1023,7 +1022,6 @@ StackExchange.Redis.IDatabaseAsync.StringLongestCommonSubsequenceLengthAsync(Sta
StackExchange.Redis.IDatabaseAsync.StringLongestCommonSubsequenceWithMatchesAsync(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, long minLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.LCSMatchResult>!
StackExchange.Redis.IDatabaseAsync.StringSetAndGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue>!
StackExchange.Redis.IDatabaseAsync.StringSetAndGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue>!
StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<bool>!
StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when) -> System.Threading.Tasks.Task<bool>!
StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task<bool>!
StackExchange.Redis.IDatabaseAsync.StringSetAsync(System.Collections.Generic.KeyValuePair<StackExchange.Redis.RedisKey, StackExchange.Redis.RedisValue>[]! values, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task<bool>!
Expand Down Expand Up @@ -2073,3 +2071,32 @@ static StackExchange.Redis.Expiration.KeepTtl.get -> StackExchange.Redis.Expirat
static StackExchange.Redis.Expiration.Persist.get -> StackExchange.Redis.Expiration
static StackExchange.Redis.Expiration.implicit operator StackExchange.Redis.Expiration(System.DateTime when) -> StackExchange.Redis.Expiration
static StackExchange.Redis.Expiration.implicit operator StackExchange.Redis.Expiration(System.TimeSpan ttl) -> StackExchange.Redis.Expiration
override StackExchange.Redis.ValueCondition.Equals(object? obj) -> bool
override StackExchange.Redis.ValueCondition.GetHashCode() -> int
override StackExchange.Redis.ValueCondition.ToString() -> string!
StackExchange.Redis.RedisFeatures.DeleteWithValueCheck.get -> bool
StackExchange.Redis.RedisFeatures.SetWithValueCheck.get -> bool
StackExchange.Redis.ValueCondition
StackExchange.Redis.ValueCondition.ValueCondition() -> void
static StackExchange.Redis.ValueCondition.Always.get -> StackExchange.Redis.ValueCondition
static StackExchange.Redis.ValueCondition.Exists.get -> StackExchange.Redis.ValueCondition
static StackExchange.Redis.ValueCondition.implicit operator StackExchange.Redis.ValueCondition(StackExchange.Redis.When when) -> StackExchange.Redis.ValueCondition
static StackExchange.Redis.ValueCondition.NotExists.get -> StackExchange.Redis.ValueCondition
static StackExchange.Redis.ValueCondition.operator !(in StackExchange.Redis.ValueCondition value) -> StackExchange.Redis.ValueCondition
StackExchange.Redis.IDatabase.StringDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
StackExchange.Redis.IDatabaseAsync.StringDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<bool>!
StackExchange.Redis.IDatabase.StringSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.Expiration expiry = default(StackExchange.Redis.Expiration), StackExchange.Redis.ValueCondition when = default(StackExchange.Redis.ValueCondition), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
StackExchange.Redis.IDatabase.StringSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, bool keepTtl, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.Expiration expiry = default(StackExchange.Redis.Expiration), StackExchange.Redis.ValueCondition when = default(StackExchange.Redis.ValueCondition), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<bool>!
StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, bool keepTtl, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<bool>!
[SER002]StackExchange.Redis.IDatabase.StringDigest(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ValueCondition?
[SER002]StackExchange.Redis.IDatabaseAsync.StringDigestAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.ValueCondition?>!
[SER002]StackExchange.Redis.ValueCondition.AsDigest() -> StackExchange.Redis.ValueCondition
[SER002]StackExchange.Redis.ValueCondition.Value.get -> StackExchange.Redis.RedisValue
[SER002]static StackExchange.Redis.ValueCondition.CalculateDigest(System.ReadOnlySpan<byte> value) -> StackExchange.Redis.ValueCondition
[SER002]static StackExchange.Redis.ValueCondition.DigestEqual(in StackExchange.Redis.RedisValue value) -> StackExchange.Redis.ValueCondition
[SER002]static StackExchange.Redis.ValueCondition.DigestNotEqual(in StackExchange.Redis.RedisValue value) -> StackExchange.Redis.ValueCondition
[SER002]static StackExchange.Redis.ValueCondition.Equal(in StackExchange.Redis.RedisValue value) -> StackExchange.Redis.ValueCondition
[SER002]static StackExchange.Redis.ValueCondition.NotEqual(in StackExchange.Redis.RedisValue value) -> StackExchange.Redis.ValueCondition
[SER002]static StackExchange.Redis.ValueCondition.ParseDigest(System.ReadOnlySpan<byte> digest) -> StackExchange.Redis.ValueCondition
[SER002]static StackExchange.Redis.ValueCondition.ParseDigest(System.ReadOnlySpan<char> digest) -> StackExchange.Redis.ValueCondition
Loading
Loading