diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index 58de2287b..e5ddbe65d 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -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))
diff --git a/src/StackExchange.Redis/Expiration.cs b/src/StackExchange.Redis/Expiration.cs
index 786cc928c..e04094358 100644
--- a/src/StackExchange.Redis/Expiration.cs
+++ b/src/StackExchange.Redis/Expiration.cs
@@ -237,7 +237,7 @@ private static void ThrowMode(ExpirationMode mode) =>
///
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,
diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs
index 5556990df..3df162682 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabase.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs
@@ -3182,7 +3182,6 @@ IEnumerable SortedSetScan(
/// The condition to enforce.
/// The flags to use for this operation.
/// See .
- [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
bool StringDelete(RedisKey key, ValueCondition when, CommandFlags flags = CommandFlags.None);
///
@@ -3411,7 +3410,7 @@ IEnumerable SortedSetScan(
/// The flags to use for this operation.
/// if the string was set, otherwise.
///
- 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);
///
/// Set to hold the string , if it matches the given condition.
@@ -3422,9 +3421,8 @@ IEnumerable SortedSetScan(
/// The condition to enforce.
/// The flags to use for this operation.
/// See .
- [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
///
diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
index b35a685a5..855ea6c8f 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
@@ -773,7 +773,6 @@ IAsyncEnumerable SortedSetScanAsync(
Task StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None);
///
- [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
Task StringDeleteAsync(RedisKey key, ValueCondition when, CommandFlags flags = CommandFlags.None);
///
@@ -840,12 +839,11 @@ IAsyncEnumerable SortedSetScanAsync(
Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags);
///
- Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None);
+ Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, bool keepTtl, When when = When.Always, CommandFlags flags = CommandFlags.None);
- ///
- [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
+ ///
#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads
- Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, ValueCondition when, CommandFlags flags = CommandFlags.None);
+ Task StringSetAsync(RedisKey key, RedisValue value, Expiration expiry = default, ValueCondition when = default, CommandFlags flags = CommandFlags.None);
#pragma warning restore RS0027
///
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
index 9119035a3..fe23b73c1 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
@@ -783,7 +783,7 @@ public Task StringIncrementAsync(RedisKey key, long value = 1, CommandFlag
public Task StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
Inner.StringLengthAsync(ToInner(key), flags);
- public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, ValueCondition when, CommandFlags flags = CommandFlags.None)
+ public Task StringSetAsync(RedisKey key, RedisValue value, Expiration expiry, ValueCondition when, CommandFlags flags = CommandFlags.None)
=> Inner.StringSetAsync(ToInner(key), value, expiry, when, flags);
public Task StringSetAsync(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
index 3c7e34aa9..69775c15d 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
@@ -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[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
diff --git a/src/StackExchange.Redis/Message.ValueCondition.cs b/src/StackExchange.Redis/Message.ValueCondition.cs
index c8b5febc4..53ddc651b 100644
--- a/src/StackExchange.Redis/Message.ValueCondition.cs
+++ b/src/StackExchange.Redis/Message.ValueCondition.cs
@@ -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(
@@ -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);
}
}
diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs
index 0eff3ff8d..386d426d8 100644
--- a/src/StackExchange.Redis/Message.cs
+++ b/src/StackExchange.Redis/Message.cs
@@ -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)
diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
index dbb710243..5eaa42b3f 100644
--- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
+++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
@@ -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[]! values, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> bool
@@ -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.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.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.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!
StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when) -> System.Threading.Tasks.Task!
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!
StackExchange.Redis.IDatabaseAsync.StringSetAsync(System.Collections.Generic.KeyValuePair[]! values, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task!
@@ -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!
+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!
+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!
+[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!
+[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 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 digest) -> StackExchange.Redis.ValueCondition
+[SER002]static StackExchange.Redis.ValueCondition.ParseDigest(System.ReadOnlySpan digest) -> StackExchange.Redis.ValueCondition
diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt
index 1b8aba3b0..91b0e1a43 100644
--- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt
@@ -1,28 +1 @@
-#nullable enable
-StackExchange.Redis.RedisFeatures.DeleteWithValueCheck.get -> bool
-StackExchange.Redis.RedisFeatures.SetWithValueCheck.get -> bool
-[SER002]override StackExchange.Redis.ValueCondition.Equals(object? obj) -> bool
-[SER002]override StackExchange.Redis.ValueCondition.GetHashCode() -> int
-[SER002]override StackExchange.Redis.ValueCondition.ToString() -> string!
-[SER002]StackExchange.Redis.IDatabase.StringDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
-[SER002]StackExchange.Redis.IDatabase.StringDigest(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ValueCondition?
-[SER002]StackExchange.Redis.IDatabase.StringSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
-[SER002]StackExchange.Redis.IDatabaseAsync.StringDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
-[SER002]StackExchange.Redis.IDatabaseAsync.StringDigestAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
-[SER002]StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.ValueCondition when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
-[SER002]StackExchange.Redis.ValueCondition
-[SER002]StackExchange.Redis.ValueCondition.AsDigest() -> StackExchange.Redis.ValueCondition
-[SER002]StackExchange.Redis.ValueCondition.Value.get -> StackExchange.Redis.RedisValue
-[SER002]StackExchange.Redis.ValueCondition.ValueCondition() -> void
-[SER002]static StackExchange.Redis.ValueCondition.Always.get -> StackExchange.Redis.ValueCondition
-[SER002]static StackExchange.Redis.ValueCondition.CalculateDigest(System.ReadOnlySpan 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.Exists.get -> StackExchange.Redis.ValueCondition
-[SER002]static StackExchange.Redis.ValueCondition.implicit operator StackExchange.Redis.ValueCondition(StackExchange.Redis.When when) -> StackExchange.Redis.ValueCondition
-[SER002]static StackExchange.Redis.ValueCondition.NotEqual(in StackExchange.Redis.RedisValue value) -> StackExchange.Redis.ValueCondition
-[SER002]static StackExchange.Redis.ValueCondition.NotExists.get -> StackExchange.Redis.ValueCondition
-[SER002]static StackExchange.Redis.ValueCondition.operator !(in StackExchange.Redis.ValueCondition value) -> StackExchange.Redis.ValueCondition
-[SER002]static StackExchange.Redis.ValueCondition.ParseDigest(System.ReadOnlySpan digest) -> StackExchange.Redis.ValueCondition
-[SER002]static StackExchange.Redis.ValueCondition.ParseDigest(System.ReadOnlySpan digest) -> StackExchange.Redis.ValueCondition
+#nullable enable
\ No newline at end of file
diff --git a/src/StackExchange.Redis/RedisDatabase.Strings.cs b/src/StackExchange.Redis/RedisDatabase.Strings.cs
index 1323246f9..6fcb7dd3b 100644
--- a/src/StackExchange.Redis/RedisDatabase.Strings.cs
+++ b/src/StackExchange.Redis/RedisDatabase.Strings.cs
@@ -48,19 +48,19 @@ private Message GetStringDeleteMessage(in RedisKey key, in ValueCondition when,
return ExecuteAsync(msg, ResultProcessor.Digest);
}
- public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, ValueCondition when, CommandFlags flags = CommandFlags.None)
+ public Task StringSetAsync(RedisKey key, RedisValue value, Expiration expiry, ValueCondition when, CommandFlags flags = CommandFlags.None)
{
var msg = GetStringSetMessage(key, value, expiry, when, flags);
return ExecuteAsync(msg, ResultProcessor.Boolean);
}
- 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)
{
var msg = GetStringSetMessage(key, value, expiry, when, flags);
return ExecuteSync(msg, ResultProcessor.Boolean);
}
- private Message GetStringSetMessage(in RedisKey key, in RedisValue value, TimeSpan? expiry, in ValueCondition when, CommandFlags flags, [CallerMemberName] string? operation = null)
+ private Message GetStringSetMessage(in RedisKey key, in RedisValue value, Expiration expiry, in ValueCondition when, CommandFlags flags, [CallerMemberName] string? operation = null)
{
switch (when.Kind)
{
diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs
index 948a9e894..f13571c4e 100644
--- a/src/StackExchange.Redis/RedisDatabase.cs
+++ b/src/StackExchange.Redis/RedisDatabase.cs
@@ -489,7 +489,7 @@ public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[]
}
private Message HashFieldGetAndSetExpiryMessage(in RedisKey key, in RedisValue hashField, Expiration expiry, CommandFlags flags) =>
- expiry.Tokens switch
+ expiry.TokenCount switch
{
// expiry, for example EX 10
2 => Message.Create(Database, flags, RedisCommand.HGETEX, key, expiry.Operand, expiry.Value, RedisLiterals.FIELDS, 1, hashField),
@@ -508,13 +508,13 @@ private Message HashFieldGetAndSetExpiryMessage(in RedisKey key, RedisValue[] ha
}
// precision, time, FIELDS, hashFields.Length
- int extraTokens = expiry.Tokens + 2;
+ int extraTokens = expiry.TokenCount + 2;
- RedisValue[] values = new RedisValue[expiry.Tokens + 2 + hashFields.Length];
+ RedisValue[] values = new RedisValue[expiry.TokenCount + 2 + hashFields.Length];
int index = 0;
// add PERSIST or expiry values
- switch (expiry.Tokens)
+ switch (expiry.TokenCount)
{
case 2:
values[index++] = expiry.Operand;
@@ -620,7 +620,7 @@ private Message HashFieldSetAndSetExpiryMessage(in RedisKey key, in RedisValue f
{
if (when == When.Always)
{
- return expiry.Tokens switch
+ return expiry.TokenCount switch
{
2 => Message.Create(Database, flags, RedisCommand.HSETEX, key, expiry.Operand, expiry.Value, RedisLiterals.FIELDS, 1, field, value),
1 => Message.Create(Database, flags, RedisCommand.HSETEX, key, expiry.Operand, RedisLiterals.FIELDS, 1, field, value),
@@ -637,7 +637,7 @@ private Message HashFieldSetAndSetExpiryMessage(in RedisKey key, in RedisValue f
_ => throw new ArgumentOutOfRangeException(nameof(when)),
};
- return expiry.Tokens switch
+ return expiry.TokenCount switch
{
2 => Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, expiry.Operand, expiry.Value, RedisLiterals.FIELDS, 1, field, value),
1 => Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, expiry.Operand, RedisLiterals.FIELDS, 1, field, value),
@@ -654,7 +654,7 @@ private Message HashFieldSetAndSetExpiryMessage(in RedisKey key, HashEntry[] has
return HashFieldSetAndSetExpiryMessage(key, field.Name, field.Value, expiry, when, flags);
}
// Determine the base array size
- var extraTokens = expiry.Tokens + (when == When.Always ? 2 : 3); // [FXX|FNX] {expiry} FIELDS {length}
+ var extraTokens = expiry.TokenCount + (when == When.Always ? 2 : 3); // [FXX|FNX] {expiry} FIELDS {length}
RedisValue[] values = new RedisValue[(hashFields.Length * 2) + extraTokens];
int index = 0;
@@ -671,7 +671,7 @@ private Message HashFieldSetAndSetExpiryMessage(in RedisKey key, HashEntry[] has
default:
throw new ArgumentOutOfRangeException(nameof(when));
}
- switch (expiry.Tokens)
+ switch (expiry.TokenCount)
{
case 2:
values[index++] = expiry.Operand;
@@ -5088,7 +5088,7 @@ private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destina
private Message GetStringGetExMessage(in RedisKey key, Expiration expiry, CommandFlags flags = CommandFlags.None)
{
- return expiry.Tokens switch
+ return expiry.TokenCount switch
{
0 => Message.Create(Database, flags, RedisCommand.GETEX, key),
1 => Message.Create(Database, flags, RedisCommand.GETEX, key, expiry.Operand),
diff --git a/src/StackExchange.Redis/StackExchange.Redis.csproj b/src/StackExchange.Redis/StackExchange.Redis.csproj
index e66b3874c..983624bc0 100644
--- a/src/StackExchange.Redis/StackExchange.Redis.csproj
+++ b/src/StackExchange.Redis/StackExchange.Redis.csproj
@@ -12,7 +12,6 @@
$(DefineConstants);VECTOR_SAFE
$(DefineConstants);UNIX_SOCKET
README.md
- $(NoWarn);SER002
diff --git a/src/StackExchange.Redis/ValueCondition.cs b/src/StackExchange.Redis/ValueCondition.cs
index 94e9850c4..d61a2f00e 100644
--- a/src/StackExchange.Redis/ValueCondition.cs
+++ b/src/StackExchange.Redis/ValueCondition.cs
@@ -8,9 +8,8 @@
namespace StackExchange.Redis;
///
-/// Represents a check for an existing value, for use in conditional operations such as DELEX or SET ... IFEQ.
+/// Represents a check for an existing value - this could be existence (NX/XX), equality (IFEQ/IFNE), or digest equality (IFDEQ/IFDNE).
///
-[Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
public readonly struct ValueCondition
{
internal enum ConditionKind : byte
@@ -108,7 +107,11 @@ public override string ToString()
///
/// Gets the underlying value for this condition.
///
- public RedisValue Value => _value;
+ public RedisValue Value
+ {
+ [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
+ get => _value;
+ }
private ValueCondition(ConditionKind kind, in RedisValue value)
{
@@ -136,26 +139,32 @@ private ValueCondition(ConditionKind kind, in RedisValue value)
///
/// Create a value equality condition with the supplied value.
///
+ [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
public static ValueCondition Equal(in RedisValue value) => new(ConditionKind.ValueEquals, value);
///
/// Create a value non-equality condition with the supplied value.
///
+ [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
+
public static ValueCondition NotEqual(in RedisValue value) => new(ConditionKind.ValueNotEquals, value);
///
/// Create a digest equality condition, computing the digest of the supplied value.
///
+ [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
public static ValueCondition DigestEqual(in RedisValue value) => value.Digest();
///
/// Create a digest non-equality condition, computing the digest of the supplied value.
///
+ [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
public static ValueCondition DigestNotEqual(in RedisValue value) => !value.Digest();
///
/// Calculate the digest of a payload, as an equality test. For a non-equality test, use on the result.
///
+ [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
public static ValueCondition CalculateDigest(ReadOnlySpan value)
{
// the internal impl of XxHash3 uses ulong (not Span), so: use
@@ -167,6 +176,7 @@ public static ValueCondition CalculateDigest(ReadOnlySpan value)
///
/// Creates an equality match based on the specified digest bytes.
///
+ [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
public static ValueCondition ParseDigest(ReadOnlySpan digest)
{
if (digest.Length != 2 * DigestBytes) ThrowDigestLength();
@@ -201,6 +211,7 @@ private static byte ParseNibble(int b)
///
/// Creates an equality match based on the specified digest bytes.
///
+ [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
public static ValueCondition ParseDigest(ReadOnlySpan digest)
{
if (digest.Length != 2 * DigestBytes) ThrowDigestLength();
@@ -341,6 +352,7 @@ internal static Span WriteHex(long value, Span target)
///
/// Convert a value condition to a digest condition.
///
+ [Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
public ValueCondition AsDigest() => _kind switch
{
ConditionKind.ValueEquals => _value.Digest(),
diff --git a/tests/RedisConfigs/start-all.sh b/tests/RedisConfigs/start-all.sh
old mode 100644
new mode 100755
diff --git a/tests/RedisConfigs/start-basic.sh b/tests/RedisConfigs/start-basic.sh
old mode 100644
new mode 100755
diff --git a/tests/StackExchange.Redis.Tests/DigestIntegrationTests.cs b/tests/StackExchange.Redis.Tests/DigestIntegrationTests.cs
index a71e2f910..ec5171075 100644
--- a/tests/StackExchange.Redis.Tests/DigestIntegrationTests.cs
+++ b/tests/StackExchange.Redis.Tests/DigestIntegrationTests.cs
@@ -4,8 +4,6 @@
namespace StackExchange.Redis.Tests;
-#pragma warning disable SER002 // 8.4
-
public class DigestIntegrationTests(ITestOutputHelper output, SharedConnectionFixture fixture)
: TestBase(output, fixture)
{
diff --git a/tests/StackExchange.Redis.Tests/DigestUnitTests.cs b/tests/StackExchange.Redis.Tests/DigestUnitTests.cs
index 9f04342d1..e1883c13b 100644
--- a/tests/StackExchange.Redis.Tests/DigestUnitTests.cs
+++ b/tests/StackExchange.Redis.Tests/DigestUnitTests.cs
@@ -7,8 +7,6 @@
namespace StackExchange.Redis.Tests;
-#pragma warning disable SER002 // 8.4
-
public class DigestUnitTests(ITestOutputHelper output) : TestBase(output)
{
[Theory]
diff --git a/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs b/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs
index fea4d4885..6012422ed 100644
--- a/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs
+++ b/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs
@@ -10,7 +10,7 @@ public void Persist_Seconds()
{
TimeSpan? time = TimeSpan.FromMilliseconds(5000);
var ex = CreateOrPersist(time, false);
- Assert.Equal(2, ex.Tokens);
+ Assert.Equal(2, ex.TokenCount);
Assert.Equal("EX 5", ex.ToString());
}
@@ -19,7 +19,7 @@ public void Persist_Milliseconds()
{
TimeSpan? time = TimeSpan.FromMilliseconds(5001);
var ex = CreateOrPersist(time, false);
- Assert.Equal(2, ex.Tokens);
+ Assert.Equal(2, ex.TokenCount);
Assert.Equal("PX 5001", ex.ToString());
}
@@ -28,7 +28,7 @@ public void Persist_None_False()
{
TimeSpan? time = null;
var ex = CreateOrPersist(time, false);
- Assert.Equal(0, ex.Tokens);
+ Assert.Equal(0, ex.TokenCount);
Assert.Equal("", ex.ToString());
}
@@ -37,7 +37,7 @@ public void Persist_None_True()
{
TimeSpan? time = null;
var ex = CreateOrPersist(time, true);
- Assert.Equal(1, ex.Tokens);
+ Assert.Equal(1, ex.TokenCount);
Assert.Equal("PERSIST", ex.ToString());
}
@@ -55,7 +55,7 @@ public void KeepTtl_Seconds()
{
TimeSpan? time = TimeSpan.FromMilliseconds(5000);
var ex = CreateOrKeepTtl(time, false);
- Assert.Equal(2, ex.Tokens);
+ Assert.Equal(2, ex.TokenCount);
Assert.Equal("EX 5", ex.ToString());
}
@@ -64,7 +64,7 @@ public void KeepTtl_Milliseconds()
{
TimeSpan? time = TimeSpan.FromMilliseconds(5001);
var ex = CreateOrKeepTtl(time, false);
- Assert.Equal(2, ex.Tokens);
+ Assert.Equal(2, ex.TokenCount);
Assert.Equal("PX 5001", ex.ToString());
}
@@ -73,7 +73,7 @@ public void KeepTtl_None_False()
{
TimeSpan? time = null;
var ex = CreateOrKeepTtl(time, false);
- Assert.Equal(0, ex.Tokens);
+ Assert.Equal(0, ex.TokenCount);
Assert.Equal("", ex.ToString());
}
@@ -82,7 +82,7 @@ public void KeepTtl_None_True()
{
TimeSpan? time = null;
var ex = CreateOrKeepTtl(time, true);
- Assert.Equal(1, ex.Tokens);
+ Assert.Equal(1, ex.TokenCount);
Assert.Equal("KEEPTTL", ex.ToString());
}
@@ -100,7 +100,7 @@ public void DateTime_Seconds()
{
var when = new DateTime(2025, 7, 23, 10, 4, 14, DateTimeKind.Utc);
var ex = new Expiration(when);
- Assert.Equal(2, ex.Tokens);
+ Assert.Equal(2, ex.TokenCount);
Assert.Equal("EXAT 1753265054", ex.ToString());
}
@@ -110,7 +110,7 @@ public void DateTime_Milliseconds()
var when = new DateTime(2025, 7, 23, 10, 4, 14, DateTimeKind.Utc);
when = when.AddMilliseconds(14);
var ex = new Expiration(when);
- Assert.Equal(2, ex.Tokens);
+ Assert.Equal(2, ex.TokenCount);
Assert.Equal("PXAT 1753265054014", ex.ToString());
}
}
diff --git a/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs b/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs
index c695488f2..0acadc74b 100644
--- a/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs
+++ b/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs
@@ -226,7 +226,7 @@ public async Task StringSet()
db.StringSet(key, val, expiresIn, When.NotExists);
db.StringSet(key, val, expiresIn, When.NotExists, flags);
- db.StringSet(key, val, null);
+ db.StringSet(key, val, expiry: default);
db.StringSet(key, val, null, When.NotExists);
db.StringSet(key, val, null, When.NotExists, flags);
@@ -241,7 +241,7 @@ public async Task StringSet()
await db.StringSetAsync(key, val, expiresIn, When.NotExists);
await db.StringSetAsync(key, val, expiresIn, When.NotExists, flags);
- await db.StringSetAsync(key, val, null);
+ await db.StringSetAsync(key, val, expiry: default);
await db.StringSetAsync(key, val, null, When.NotExists);
await db.StringSetAsync(key, val, null, When.NotExists, flags);
}
diff --git a/tests/StackExchange.Redis.Tests/StringTests.cs b/tests/StackExchange.Redis.Tests/StringTests.cs
index 85bcc7dd5..1ade532d7 100644
--- a/tests/StackExchange.Redis.Tests/StringTests.cs
+++ b/tests/StackExchange.Redis.Tests/StringTests.cs
@@ -301,9 +301,9 @@ public async Task SetKeepTtl()
Assert.True(await x2 > TimeSpan.FromMinutes(9), "Over 9");
Assert.True(await x2 <= TimeSpan.FromMinutes(10), "Under 10");
- db.StringSet(prefix + "1", "def", keepTtl: true, flags: CommandFlags.FireAndForget);
+ db.StringSet(prefix + "1", "def", Expiration.KeepTtl, flags: CommandFlags.FireAndForget);
db.StringSet(prefix + "2", "def", flags: CommandFlags.FireAndForget);
- db.StringSet(prefix + "3", "def", keepTtl: true, flags: CommandFlags.FireAndForget);
+ db.StringSet(prefix + "3", "def", Expiration.KeepTtl, flags: CommandFlags.FireAndForget);
var y0 = db.KeyTimeToLiveAsync(prefix + "1");
var y1 = db.KeyTimeToLiveAsync(prefix + "2");