From 390d1f48980e32259a09731f252858067ab94356 Mon Sep 17 00:00:00 2001 From: atakavci Date: Fri, 7 Feb 2025 16:46:43 +0300 Subject: [PATCH 01/19] Update CI.yml --- .github/workflows/CI.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 58856abbc..6725171c5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,6 +1,7 @@ name: CI on: + workflow_dispatch: pull_request: push: branches: From dad0b17df5f8cf67deaa5d91d3e9a58b6d9e5d2f Mon Sep 17 00:00:00 2001 From: atakavci Date: Thu, 20 Feb 2025 16:58:33 +0300 Subject: [PATCH 02/19] Support for HFE API commands, HGETDEL, HGETEX, HSETEX --- src/StackExchange.Redis/Enums/RedisCommand.cs | 6 + .../Interfaces/IDatabase.cs | 170 +++++++++ .../Interfaces/IDatabaseAsync.cs | 45 +++ .../PublicAPI/PublicAPI.Shipped.txt | 31 +- src/StackExchange.Redis/RedisDatabase.cs | 342 ++++++++++++++++++ src/StackExchange.Redis/RedisLiterals.cs | 2 + 6 files changed, 595 insertions(+), 1 deletion(-) diff --git a/src/StackExchange.Redis/Enums/RedisCommand.cs b/src/StackExchange.Redis/Enums/RedisCommand.cs index a4647d7eb..28c484fbf 100644 --- a/src/StackExchange.Redis/Enums/RedisCommand.cs +++ b/src/StackExchange.Redis/Enums/RedisCommand.cs @@ -70,6 +70,8 @@ internal enum RedisCommand HEXPIREAT, HEXPIRETIME, HGET, + HGETEX, + HGETDEL, HGETALL, HINCRBY, HINCRBYFLOAT, @@ -85,6 +87,7 @@ internal enum RedisCommand HRANDFIELD, HSCAN, HSET, + HSETEX, HSETNX, HSTRLEN, HVALS, @@ -289,6 +292,8 @@ internal static bool IsPrimaryOnly(this RedisCommand command) case RedisCommand.HDEL: case RedisCommand.HEXPIRE: case RedisCommand.HEXPIREAT: + case RedisCommand.HGETDEL: + case RedisCommand.HGETEX: case RedisCommand.HINCRBY: case RedisCommand.HINCRBYFLOAT: case RedisCommand.HMSET: @@ -296,6 +301,7 @@ internal static bool IsPrimaryOnly(this RedisCommand command) case RedisCommand.HPEXPIRE: case RedisCommand.HPEXPIREAT: case RedisCommand.HSET: + case RedisCommand.HSETEX: case RedisCommand.HSETNX: case RedisCommand.INCR: case RedisCommand.INCRBY: diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs index 207c03326..f315896bf 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabase.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -516,6 +516,176 @@ public interface IDatabase : IRedis, IDatabaseAsync /// RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + /// + /// Returns the value associated with field in the hash stored at key. + /// + /// The key of the hash. + /// The field in the hash to get. + /// The flags to use for this operation. + /// The value associated with field, or when field is not present in the hash or key does not exist. + /// + RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the value associated with field in the hash stored at key. + /// + /// The key of the hash. + /// The field in the hash to get. + /// The flags to use for this operation. + /// The value associated with field, or when field is not present in the hash or key does not exist. + /// + Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the values associated with the specified fields in the hash stored at key. + /// For every field that does not exist in the hash, a value is returned. + /// Because non-existing keys are treated as empty hashes, running HMGET against a non-existing key will return a list of values. + /// + /// The key of the hash. + /// The fields in the hash to get. + /// The flags to use for this operation. + /// List of values associated with the given fields, in the same order as they are requested. + /// + RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of the specified hash field and sets its expiration time. + /// + /// The key of the hash. + /// The field in the hash to get and set the expiration for. + /// The expiration time to set. + /// If true, the expiration will be removed. And 'expiry' parameter is ignored. + /// The flags to use for this operation. + /// The value of the specified hash field. + RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of the specified hash field and sets its expiration time. + /// + /// The key of the hash. + /// The field in the hash to get and set the expiration for. + /// The exact date and time to set the expiration to. + /// If true, the expiration will be removed. And 'expiry' parameter is ignored. + /// The flags to use for this operation. + /// The value of the specified hash field. + RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of the specified hash field and sets its expiration time, returning a lease. + /// + /// The key of the hash. + /// The field in the hash to get and set the expiration for. + /// The expiration time to set. + /// If true, the expiration will be removed. And 'expiry' parameter is ignored. + /// The flags to use for this operation. + /// The value of the specified hash field as a lease. + Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of the specified hash field and sets its expiration time, returning a lease. + /// + /// The key of the hash. + /// The field in the hash to get and set the expiration for. + /// The exact date and time to set the expiration to. + /// If true, the expiration will be removed. And 'expiry' parameter is ignored. + /// The flags to use for this operation. + /// The value of the specified hash field as a lease. + Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the values of the specified hash fields and sets their expiration times. + /// + /// The key of the hash. + /// The fields in the hash to get and set the expiration for. + /// The expiration time to set. + /// If true, the expiration will be removed. And 'expiry' parameter is ignored. + /// The flags to use for this operation. + /// The values of the specified hash fields. + RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the values of the specified hash fields and sets their expiration times. + /// + /// The key of the hash. + /// The fields in the hash to get and set the expiration for. + /// The exact date and time to set the expiration to. + /// If true, the expiration will be removed. And 'expiry' parameter is ignored. + /// The flags to use for this operation. + /// The values of the specified hash fields. + RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the value of the specified hash field and sets its expiration time. + /// + /// The key of the hash. + /// The field in the hash to set and set the expiration for. + /// The expiration time to set. + /// Whether to maintain the existing field's TTL (KEEPTTL flag). + /// Which conditions to set the value under (defaults to always). + /// The flags to use for this operation. + /// The value of the specified hash field. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the value of the specified hash field and sets its expiration time. + /// + /// The key of the hash. + /// The field in the hash to set and set the expiration for. + /// The exact date and time to set the expiration to. + /// Whether to maintain the existing field's TTL (KEEPTTL flag). + /// Which conditions to set the value under (defaults to always). + /// The flags to use for this operation. + /// The value of the specified hash field. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the value of the specified hash field and sets its expiration time, returning a lease. + /// + /// The key of the hash. + /// The field in the hash to set and set the expiration for. + /// The expiration time to set. + /// Whether to maintain the existing field's TTL (KEEPTTL flag). + /// Which conditions to set the value under (defaults to always). + /// The flags to use for this operation. + /// The value of the specified hash field as a lease. + Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the value of the specified hash field and sets its expiration time, returning a lease. + /// + /// The key of the hash. + /// The field in the hash to set and set the expiration for. + /// The exact date and time to set the expiration to. + /// Whether to maintain the existing field's TTL (KEEPTTL flag). + /// Which conditions to set the value under (defaults to always). + /// The flags to use for this operation. + /// The value of the specified hash field as a lease. + Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the values of the specified hash fields and sets their expiration times. + /// + /// The key of the hash. + /// The fields in the hash to set and set the expiration for. + /// The expiration time to set. + /// Whether to maintain the existing fields' TTL (KEEPTTL flag). + /// Which conditions to set the values under (defaults to always). + /// The flags to use for this operation. + /// The values of the specified hash fields. + RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the values of the specified hash fields and sets their expiration times. + /// + /// The key of the hash. + /// The fields in the hash to set and set the expiration for. + /// The exact date and time to set the expiration to. + /// Whether to maintain the existing fields' TTL (KEEPTTL flag). + /// Which conditions to set the values under (defaults to always). + /// The flags to use for this operation. + /// The values of the specified hash fields. + RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// /// Returns all fields and values of the hash stored at key. /// diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs index 9852c131c..6a6c2339a 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -84,6 +84,51 @@ public interface IDatabaseAsync : IRedisAsync /// Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + /// + Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index a24333c8e..49c644e5c 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -1893,4 +1893,33 @@ virtual StackExchange.Redis.RedisResult.Length.get -> int virtual StackExchange.Redis.RedisResult.this[int index].get -> StackExchange.Redis.RedisResult! StackExchange.Redis.ConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void StackExchange.Redis.IConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void - +StackExchange.Redis.IDatabase.HashFieldGetAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldGetAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashFieldGetLeaseAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashFieldGetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashFieldGetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashFieldSetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashFieldSetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? 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.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, 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.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime? 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.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, 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.HashFieldSetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? 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.HashFieldSetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, 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?>! diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 7468bdb64..65752b911 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -446,6 +446,348 @@ private T HashFieldExecute(RedisCommand cmd, RedisKey key, Custom private Task AsyncCustomArrExecutor(Message msg, TProcessor processor) where TProcessor : ResultProcessor => ExecuteAsync(msg, processor)!; + public RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashField); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashField); + return ExecuteSync(msg, ResultProcessor.Lease); + } + + public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return Array.Empty(); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashFields); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashField); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashField); + return ExecuteAsync(msg, ResultProcessor.Lease); + } + + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashFields); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + private delegate (RedisValue Precision, RedisValue Duration) CalculateExpiryArgs(T expiry); + + private (RedisValue Precision, RedisValue Time) CalculateExpiryValues(TimeSpan expiry) + { + long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond; + var useSeconds = milliseconds % 1000 == 0; + return useSeconds ? (RedisLiterals.EX, milliseconds / 1000) : (RedisLiterals.PX, milliseconds); + } + + private (RedisValue Precision, RedisValue Time) CalculateExpiryValues(DateTime expiry) + { + long milliseconds = GetMillisecondsUntil(expiry); + var useSeconds = milliseconds % 1000 == 0; + return useSeconds ? (RedisLiterals.EXAT, milliseconds / 1000) : (RedisLiterals.PXAT, milliseconds); + } + + private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue hashField, T? expiry, CalculateExpiryArgs calculateExpiryArgs, bool persist, CommandFlags flags) where T : struct + { + if (expiry != null && persist) + { + throw new ArgumentException("Cannot specify both expiry and persist"); + } + switch ((expiry, persist)) + { + case (_, true): // Case when persist is true (expiry is disregarded) + return Message.Create(Database, flags, RedisCommand.HGETEX, key, RedisLiterals.PERSIST, RedisLiterals.FIELDS, 1, hashField); + case (not null, _): // Case when expiry is not null + var (precision, duration) = calculateExpiryArgs((T)expiry!); + return Message.Create(Database, flags, RedisCommand.HGETEX, key, precision, duration, RedisLiterals.FIELDS, 1, hashField); + default: // Default case when both expiry and persist are default + return Message.Create(Database, flags, RedisCommand.HGETEX, RedisLiterals.FIELDS, 1, hashField); + } + } + + private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, bool persist, CommandFlags flags) where T : struct + { + if (expiry != null && persist) + { + throw new ArgumentException("Cannot specify both expiry and persist"); + } + List? values = null; + switch ((expiry, persist)) + { + case (_, true): // Case when persist is true (expiry is disregarded) + values = new List { RedisLiterals.PERSIST, RedisLiterals.FIELDS, hashFields.Length }; + break; + case (not null, _): // Case when expiry is not null + var (precision, duration) = calculateExpiryArgs((T)expiry!); + values = new List { precision, duration, RedisLiterals.FIELDS, hashFields.Length }; + break; + default: // Default case when both expiry and persist are default + values = new List { RedisLiterals.FIELDS, hashFields.Length }; + break; + } + values.AddRange(hashFields); + return Message.Create(Database, flags, RedisCommand.HGETEX, key, values.ToArray()); + } + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + return ExecuteSync(msg, ResultProcessor.Lease); + } + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + return ExecuteSync(msg, ResultProcessor.Lease); + } + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return Array.Empty(); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return Array.Empty(); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + return ExecuteAsync(msg, ResultProcessor.Lease); + } + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + return ExecuteAsync(msg, ResultProcessor.Lease); + } + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue hashField, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct + { + if (expiry != null && keepTtl) + { + throw new ArgumentException("Cannot specify both expiry and keepTtl"); + } + switch ((when, expiry, keepTtl)) + { + case (When.Always, _, true): // Case when keepTtl is true (expiry is disregarded) + return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, hashField); + case (When.Always, not null, _): // Case when expiry is not null + { + var (precision, duration) = calculateExpiryArgs((T)expiry!); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, precision, duration, RedisLiterals.FIELDS, 1, hashField); + } + case (When.Always, _, _): // Default case when both expiry and keepTtl are default + return Message.Create(Database, flags, RedisCommand.HSETEX, RedisLiterals.FIELDS, 1, hashField); + + case (When.Exists, _, true): // Case when keepTtl is true (expiry is disregarded) + case (When.NotExists, _, true): + { + var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, hashField); + } + case (When.Exists, not null, _): // Case when expiry is not null + case (When.NotExists, not null, _): + { + var (precision, duration) = calculateExpiryArgs((T)expiry!); + var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, precision, duration, RedisLiterals.FIELDS, 1, hashField); + } + default: // Only existance is specified + { + var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.FIELDS, 1, hashField); + } + } + } + + private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct + { + if (expiry != null && keepTtl) + { + throw new ArgumentException("Cannot specify both expiry and keepTtl"); + } + List? values = null; + switch ((when, expiry, keepTtl)) + { + case (When.Always, _, true): // Case when keepTtl is true (expiry is disregarded) + values = new List { RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, hashFields.Length }; + break; + case (When.Always, not null, _): // Case when expiry is not null + { + var (precision, duration) = calculateExpiryArgs((T)expiry!); + values = new List { precision, duration, RedisLiterals.FIELDS, hashFields.Length }; + } + break; + case (When.Always, _, _): // Default case when both expiry and keepTtl are default + values = new List { RedisLiterals.FIELDS, hashFields.Length }; + break; + case (When.Exists, _, true): // Case when keepTtl is true (expiry is disregarded) + case (When.NotExists, _, true): + { + var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; + values = new List { existance, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, hashFields.Length }; + } + break; + case (When.Exists, not null, _): // Case when expiry is not null + case (When.NotExists, not null, _): + { + var (precision, duration) = calculateExpiryArgs((T)expiry!); + var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; + values = new List { existance, precision, duration, RedisLiterals.FIELDS, hashFields.Length }; + } + break; + default: // Only existance is specified + { + var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; + values = new List { existance, RedisLiterals.FIELDS, hashFields.Length }; + } + break; + } + values.AddRange(hashFields); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, values.ToArray()); + } + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteSync(msg, ResultProcessor.Lease); + } + + public Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteSync(msg, ResultProcessor.Lease); + } + + public RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return Array.Empty(); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + public RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return Array.Empty(); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteAsync(msg, ResultProcessor.Lease); + } + + public Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteAsync(msg, ResultProcessor.Lease); + } + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => HashFieldExecute(RedisCommand.HPEXPIRETIME, key, SyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields); diff --git a/src/StackExchange.Redis/RedisLiterals.cs b/src/StackExchange.Redis/RedisLiterals.cs index 549691fd2..d70c55296 100644 --- a/src/StackExchange.Redis/RedisLiterals.cs +++ b/src/StackExchange.Redis/RedisLiterals.cs @@ -81,7 +81,9 @@ public static readonly RedisValue FIELDS = "FIELDS", FILTERBY = "FILTERBY", FLUSH = "FLUSH", + FNX = "FNX", FREQ = "FREQ", + FXX = "FXX", GET = "GET", GETKEYS = "GETKEYS", GETNAME = "GETNAME", From d29b19fa159eb2cb95b6c2ade65131400b6b2e15 Mon Sep 17 00:00:00 2001 From: atakavci Date: Tue, 4 Mar 2025 10:58:14 +0300 Subject: [PATCH 03/19] adding KeyPrefixed --- .../KeyspaceIsolation/KeyPrefixed.cs | 45 +++++++++++++++++++ .../KeyspaceIsolation/KeyPrefixedDatabase.cs | 45 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index b97bba73b..c1041495c 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -84,6 +84,51 @@ public Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFla public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => Inner.HashExistsAsync(ToInner(key), hashField, flags); + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndDeleteAsync(key, hashField, flags); + + public Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndDeleteAsync(key, hashField, flags); + + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndDeleteAsync(key, hashFields, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(key, hashField, expiry, persist, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(key, hashField, expiry, persist, flags); + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, persist, flags); + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, persist, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, persist, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, persist, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(key, hashField, expiry, keepTtl, when, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(key, hashField, expiry, keepTtl, when, flags); + + public Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetLeaseAndSetExpiryAsync(key, hashField, expiry, keepTtl, when, flags); + + public Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetLeaseAndSetExpiryAsync(key, hashField, expiry, keepTtl, when, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, keepTtl, when, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, keepTtl, when, flags); + public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs index 75d93d0f9..ff0649dd3 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs @@ -81,6 +81,51 @@ public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => Inner.HashExists(ToInner(key), hashField, flags); + public RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndDelete(key, hashField, flags); + + public Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndDelete(key, hashField, flags); + + public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndDelete(key, hashFields, flags); + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(key, hashField, expiry, persist, flags); + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(key, hashField, expiry, persist, flags); + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, persist, flags); + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, persist, flags); + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(key, hashFields, expiry, persist, flags); + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(key, hashFields, expiry, persist, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(key, hashField, expiry, keepTtl, when, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(key, hashField, expiry, keepTtl, when, flags); + + public Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetLeaseAndSetExpiry(key, hashField, expiry, keepTtl, when, flags); + + public Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetLeaseAndSetExpiry(key, hashField, expiry, keepTtl, when, flags); + + public RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(key, hashFields, expiry, keepTtl, when, flags); + + public RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(key, hashFields, expiry, keepTtl, when, flags); + public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags); From bfa5aa0aa95f9a43c96fa51b14c3361f05d501ed Mon Sep 17 00:00:00 2001 From: atakavci Date: Tue, 4 Mar 2025 11:36:35 +0300 Subject: [PATCH 04/19] switch to out params --- src/StackExchange.Redis/RedisDatabase.cs | 36 +++++++++++++----------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 65752b911..cb3354eb5 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -486,20 +486,22 @@ public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } - private delegate (RedisValue Precision, RedisValue Duration) CalculateExpiryArgs(T expiry); + private delegate void CalculateExpiryArgs(T expiry, out RedisValue precision, out RedisValue time); - private (RedisValue Precision, RedisValue Time) CalculateExpiryValues(TimeSpan expiry) + private void CalculateExpiryValues(TimeSpan expiry, out RedisValue precision, out RedisValue time) { long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond; var useSeconds = milliseconds % 1000 == 0; - return useSeconds ? (RedisLiterals.EX, milliseconds / 1000) : (RedisLiterals.PX, milliseconds); + precision = useSeconds ? RedisLiterals.EX : RedisLiterals.PX; + time = useSeconds ? (milliseconds / 1000) : milliseconds; } - private (RedisValue Precision, RedisValue Time) CalculateExpiryValues(DateTime expiry) + private void CalculateExpiryValues(DateTime expiry, out RedisValue precision, out RedisValue time) { long milliseconds = GetMillisecondsUntil(expiry); var useSeconds = milliseconds % 1000 == 0; - return useSeconds ? (RedisLiterals.EXAT, milliseconds / 1000) : (RedisLiterals.PXAT, milliseconds); + precision = useSeconds ? RedisLiterals.EXAT : RedisLiterals.PXAT; + time = useSeconds ? (milliseconds / 1000) : milliseconds; } private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue hashField, T? expiry, CalculateExpiryArgs calculateExpiryArgs, bool persist, CommandFlags flags) where T : struct @@ -513,8 +515,8 @@ private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue hash case (_, true): // Case when persist is true (expiry is disregarded) return Message.Create(Database, flags, RedisCommand.HGETEX, key, RedisLiterals.PERSIST, RedisLiterals.FIELDS, 1, hashField); case (not null, _): // Case when expiry is not null - var (precision, duration) = calculateExpiryArgs((T)expiry!); - return Message.Create(Database, flags, RedisCommand.HGETEX, key, precision, duration, RedisLiterals.FIELDS, 1, hashField); + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); + return Message.Create(Database, flags, RedisCommand.HGETEX, key, precision, time, RedisLiterals.FIELDS, 1, hashField); default: // Default case when both expiry and persist are default return Message.Create(Database, flags, RedisCommand.HGETEX, RedisLiterals.FIELDS, 1, hashField); } @@ -533,8 +535,8 @@ private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue[] ha values = new List { RedisLiterals.PERSIST, RedisLiterals.FIELDS, hashFields.Length }; break; case (not null, _): // Case when expiry is not null - var (precision, duration) = calculateExpiryArgs((T)expiry!); - values = new List { precision, duration, RedisLiterals.FIELDS, hashFields.Length }; + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); + values = new List { precision, time, RedisLiterals.FIELDS, hashFields.Length }; break; default: // Default case when both expiry and persist are default values = new List { RedisLiterals.FIELDS, hashFields.Length }; @@ -636,8 +638,8 @@ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue hash return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, hashField); case (When.Always, not null, _): // Case when expiry is not null { - var (precision, duration) = calculateExpiryArgs((T)expiry!); - return Message.Create(Database, flags, RedisCommand.HSETEX, key, precision, duration, RedisLiterals.FIELDS, 1, hashField); + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, precision, time, RedisLiterals.FIELDS, 1, hashField); } case (When.Always, _, _): // Default case when both expiry and keepTtl are default return Message.Create(Database, flags, RedisCommand.HSETEX, RedisLiterals.FIELDS, 1, hashField); @@ -651,9 +653,9 @@ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue hash case (When.Exists, not null, _): // Case when expiry is not null case (When.NotExists, not null, _): { - var (precision, duration) = calculateExpiryArgs((T)expiry!); + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, precision, duration, RedisLiterals.FIELDS, 1, hashField); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, precision, time, RedisLiterals.FIELDS, 1, hashField); } default: // Only existance is specified { @@ -677,8 +679,8 @@ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue[] ha break; case (When.Always, not null, _): // Case when expiry is not null { - var (precision, duration) = calculateExpiryArgs((T)expiry!); - values = new List { precision, duration, RedisLiterals.FIELDS, hashFields.Length }; + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); + values = new List { precision, time, RedisLiterals.FIELDS, hashFields.Length }; } break; case (When.Always, _, _): // Default case when both expiry and keepTtl are default @@ -694,9 +696,9 @@ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue[] ha case (When.Exists, not null, _): // Case when expiry is not null case (When.NotExists, not null, _): { - var (precision, duration) = calculateExpiryArgs((T)expiry!); + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - values = new List { existance, precision, duration, RedisLiterals.FIELDS, hashFields.Length }; + values = new List { existance, precision, time, RedisLiterals.FIELDS, hashFields.Length }; } break; default: // Only existance is specified From 8d604133e6428a050d955b21d21352bb2f58dbd5 Mon Sep 17 00:00:00 2001 From: atakavci Date: Tue, 4 Mar 2025 16:51:05 +0300 Subject: [PATCH 05/19] replace list with array --- src/StackExchange.Redis/RedisDatabase.cs | 203 ++++++++++++----------- 1 file changed, 107 insertions(+), 96 deletions(-) diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index cb3354eb5..2ea93fc65 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -506,44 +506,62 @@ private void CalculateExpiryValues(DateTime expiry, out RedisValue precision, ou private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue hashField, T? expiry, CalculateExpiryArgs calculateExpiryArgs, bool persist, CommandFlags flags) where T : struct { - if (expiry != null && persist) + if (expiry != null && persist) throw new ArgumentException("Cannot specify both expiry and persist"); + + if (persist) // Case when persist is true (expiry is disregarded) { - throw new ArgumentException("Cannot specify both expiry and persist"); + return Message.Create(Database, flags, RedisCommand.HGETEX, key, RedisLiterals.PERSIST, RedisLiterals.FIELDS, 1, hashField); } - switch ((expiry, persist)) + + if (expiry != null) // Check if expiry is not null { - case (_, true): // Case when persist is true (expiry is disregarded) - return Message.Create(Database, flags, RedisCommand.HGETEX, key, RedisLiterals.PERSIST, RedisLiterals.FIELDS, 1, hashField); - case (not null, _): // Case when expiry is not null - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - return Message.Create(Database, flags, RedisCommand.HGETEX, key, precision, time, RedisLiterals.FIELDS, 1, hashField); - default: // Default case when both expiry and persist are default - return Message.Create(Database, flags, RedisCommand.HGETEX, RedisLiterals.FIELDS, 1, hashField); + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); + return Message.Create(Database, flags, RedisCommand.HGETEX, key, precision, time, RedisLiterals.FIELDS, 1, hashField); } + + // Default case when neither expiry nor persist are set + return Message.Create(Database, flags, RedisCommand.HGETEX, RedisLiterals.FIELDS, 1, hashField); } private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, bool persist, CommandFlags flags) where T : struct { - if (expiry != null && persist) + if (expiry != null && persist) throw new ArgumentException("Cannot specify both expiry and persist"); + + // Calculate the total size of the array based on conditions + int arraySize = 0; + if (persist) // Case when persist is true (expiry is disregarded) { - throw new ArgumentException("Cannot specify both expiry and persist"); + arraySize = 3; // PERSIST, FIELDS, hashFields.Length } - List? values = null; - switch ((expiry, persist)) + else if (expiry != null) // Case when expiry is not null { - case (_, true): // Case when persist is true (expiry is disregarded) - values = new List { RedisLiterals.PERSIST, RedisLiterals.FIELDS, hashFields.Length }; - break; - case (not null, _): // Case when expiry is not null - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - values = new List { precision, time, RedisLiterals.FIELDS, hashFields.Length }; - break; - default: // Default case when both expiry and persist are default - values = new List { RedisLiterals.FIELDS, hashFields.Length }; - break; + arraySize = 4; // precision, time, FIELDS, hashFields.Length } - values.AddRange(hashFields); - return Message.Create(Database, flags, RedisCommand.HGETEX, key, values.ToArray()); + else // Default case when both expiry and persist are default + { + arraySize = 2; // FIELDS, hashFields.Length + } + + // Create an array to hold the values (including hashFields) + RedisValue[] values = new RedisValue[arraySize + hashFields.Length]; + + int index = 0; + // Add PERSIST or expiry values, or just FIELDS + if (persist) // Case when persist is true (expiry is disregarded) + { + values[index++] = RedisLiterals.PERSIST; + } + else if (expiry != null) // Check if expiry is not null + { + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); + values[index++] = precision; + values[index++] = time; + } + values[index++] = RedisLiterals.FIELDS; + values[index++] = hashFields.Length; + // Add hash fields to the array + Array.Copy(hashFields, 0, values, index, hashFields.Length); + return Message.Create(Database, flags, RedisCommand.HGETEX, key, values); } public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) @@ -628,88 +646,81 @@ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue hashField, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct { - if (expiry != null && keepTtl) - { - throw new ArgumentException("Cannot specify both expiry and keepTtl"); - } - switch ((when, expiry, keepTtl)) + if (expiry != null && keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl"); + + if (when == When.Always) { - case (When.Always, _, true): // Case when keepTtl is true (expiry is disregarded) + if (keepTtl) // Case when keepTtl is true (expiry is disregarded) + { return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, hashField); - case (When.Always, not null, _): // Case when expiry is not null - { - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - return Message.Create(Database, flags, RedisCommand.HSETEX, key, precision, time, RedisLiterals.FIELDS, 1, hashField); - } - case (When.Always, _, _): // Default case when both expiry and keepTtl are default - return Message.Create(Database, flags, RedisCommand.HSETEX, RedisLiterals.FIELDS, 1, hashField); + } - case (When.Exists, _, true): // Case when keepTtl is true (expiry is disregarded) - case (When.NotExists, _, true): - { - var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, hashField); - } - case (When.Exists, not null, _): // Case when expiry is not null - case (When.NotExists, not null, _): - { - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, precision, time, RedisLiterals.FIELDS, 1, hashField); - } - default: // Only existance is specified - { - var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.FIELDS, 1, hashField); - } + if (expiry != null) // Case when expiry is not null + { + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, precision, time, RedisLiterals.FIELDS, 1, hashField); + } + // Default case when both expiry and keepTtl are default + return Message.Create(Database, flags, RedisCommand.HSETEX, RedisLiterals.FIELDS, 1, hashField); } + + var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; + if (keepTtl) // Case when keepTtl is true (expiry is disregarded) + { + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, hashField); + } + if (expiry != null) // Case when expiry is not null + { + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, precision, time, RedisLiterals.FIELDS, 1, hashField); + } + // Only existance is specified + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.FIELDS, 1, hashField); } private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct { - if (expiry != null && keepTtl) + if (expiry != null && keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl"); + + // Determine the base array size + int arraySize = when == When.Always ? 0 : 1; + + if (keepTtl) { - throw new ArgumentException("Cannot specify both expiry and keepTtl"); + arraySize += 3; // KEEPTTL, FIELDS, hashFields.Length } - List? values = null; - switch ((when, expiry, keepTtl)) + else if (expiry != null) { - case (When.Always, _, true): // Case when keepTtl is true (expiry is disregarded) - values = new List { RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, hashFields.Length }; - break; - case (When.Always, not null, _): // Case when expiry is not null - { - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - values = new List { precision, time, RedisLiterals.FIELDS, hashFields.Length }; - } - break; - case (When.Always, _, _): // Default case when both expiry and keepTtl are default - values = new List { RedisLiterals.FIELDS, hashFields.Length }; - break; - case (When.Exists, _, true): // Case when keepTtl is true (expiry is disregarded) - case (When.NotExists, _, true): - { - var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - values = new List { existance, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, hashFields.Length }; - } - break; - case (When.Exists, not null, _): // Case when expiry is not null - case (When.NotExists, not null, _): - { - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - values = new List { existance, precision, time, RedisLiterals.FIELDS, hashFields.Length }; - } - break; - default: // Only existance is specified - { - var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - values = new List { existance, RedisLiterals.FIELDS, hashFields.Length }; - } - break; + arraySize += 4; // precision, time, FIELDS, hashFields.Length } - values.AddRange(hashFields); - return Message.Create(Database, flags, RedisCommand.HSETEX, key, values.ToArray()); + else + { + arraySize += 2; // FIELDS, hashFields.Length + } + + arraySize += hashFields.Length; + RedisValue[] values = new RedisValue[arraySize]; + int index = 0; + + if (when != When.Always) + { + values[index++] = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; + } + + if (keepTtl) // Case when keepTtl is true (expiry is disregarded) + { + values[index++] = RedisLiterals.KEEPTTL; + } + else if (expiry != null) // Case when expiry is not null + { + calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); + values[index++] = precision; + values[index++] = time; + } + values[index++] = RedisLiterals.FIELDS; + values[index++] = hashFields.Length; + Array.Copy(hashFields, 0, values, index, hashFields.Length); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, values); } public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) From ee91dc8fd75b2c6c6831b9be1765b237f1ad3d2c Mon Sep 17 00:00:00 2001 From: atakavci Date: Wed, 12 Mar 2025 02:09:04 +0300 Subject: [PATCH 06/19] - fix parameter issues for hashsetand... commands - add tests --- .../Interfaces/IDatabase.cs | 57 +--- .../Interfaces/IDatabaseAsync.cs | 34 +-- .../KeyspaceIsolation/KeyPrefixed.cs | 32 +-- .../KeyspaceIsolation/KeyPrefixedDatabase.cs | 32 +-- .../PublicAPI/PublicAPI.Shipped.txt | 32 +-- src/StackExchange.Redis/RedisDatabase.cs | 112 +++----- src/StackExchange.Redis/RedisFeatures.cs | 3 +- .../HashFieldTests.cs | 266 ++++++++++++++++++ 8 files changed, 381 insertions(+), 187 deletions(-) diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs index f315896bf..fcf581473 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabase.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -565,10 +565,9 @@ public interface IDatabase : IRedis, IDatabaseAsync /// The key of the hash. /// The field in the hash to get and set the expiration for. /// The exact date and time to set the expiration to. - /// If true, the expiration will be removed. And 'expiry' parameter is ignored. /// The flags to use for this operation. /// The value of the specified hash field. - RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None); /// /// Gets the value of the specified hash field and sets its expiration time, returning a lease. @@ -587,10 +586,9 @@ public interface IDatabase : IRedis, IDatabaseAsync /// The key of the hash. /// The field in the hash to get and set the expiration for. /// The exact date and time to set the expiration to. - /// If true, the expiration will be removed. And 'expiry' parameter is ignored. /// The flags to use for this operation. /// The value of the specified hash field as a lease. - Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None); /// /// Gets the values of the specified hash fields and sets their expiration times. @@ -609,58 +607,34 @@ public interface IDatabase : IRedis, IDatabaseAsync /// The key of the hash. /// The fields in the hash to get and set the expiration for. /// The exact date and time to set the expiration to. - /// If true, the expiration will be removed. And 'expiry' parameter is ignored. /// The flags to use for this operation. /// The values of the specified hash fields. - RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None); /// /// Sets the value of the specified hash field and sets its expiration time. /// /// The key of the hash. - /// The field in the hash to set and set the expiration for. + /// The field in the hash to set and set the expiration for. + /// The value in the hash to set and set the expiration for. /// The expiration time to set. /// Whether to maintain the existing field's TTL (KEEPTTL flag). /// Which conditions to set the value under (defaults to always). /// The flags to use for this operation. - /// The value of the specified hash field. - RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// 0 if no fields were set, 1 if all the fields were set. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); /// /// Sets the value of the specified hash field and sets its expiration time. /// /// The key of the hash. - /// The field in the hash to set and set the expiration for. + /// The field in the hash to set and set the expiration for. + /// The value in the hash to set and set the expiration for. /// The exact date and time to set the expiration to. - /// Whether to maintain the existing field's TTL (KEEPTTL flag). /// Which conditions to set the value under (defaults to always). /// The flags to use for this operation. - /// The value of the specified hash field. - RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Sets the value of the specified hash field and sets its expiration time, returning a lease. - /// - /// The key of the hash. - /// The field in the hash to set and set the expiration for. - /// The expiration time to set. - /// Whether to maintain the existing field's TTL (KEEPTTL flag). - /// Which conditions to set the value under (defaults to always). - /// The flags to use for this operation. - /// The value of the specified hash field as a lease. - Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Sets the value of the specified hash field and sets its expiration time, returning a lease. - /// - /// The key of the hash. - /// The field in the hash to set and set the expiration for. - /// The exact date and time to set the expiration to. - /// Whether to maintain the existing field's TTL (KEEPTTL flag). - /// Which conditions to set the value under (defaults to always). - /// The flags to use for this operation. - /// The value of the specified hash field as a lease. - Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// 0 if no fields were set, 1 if all the fields were set. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None); /// /// Sets the values of the specified hash fields and sets their expiration times. @@ -671,8 +645,8 @@ public interface IDatabase : IRedis, IDatabaseAsync /// Whether to maintain the existing fields' TTL (KEEPTTL flag). /// Which conditions to set the values under (defaults to always). /// The flags to use for this operation. - /// The values of the specified hash fields. - RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// 0 if no fields were set, 1 if all the fields were set. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); /// /// Sets the values of the specified hash fields and sets their expiration times. @@ -680,11 +654,10 @@ public interface IDatabase : IRedis, IDatabaseAsync /// The key of the hash. /// The fields in the hash to set and set the expiration for. /// The exact date and time to set the expiration to. - /// Whether to maintain the existing fields' TTL (KEEPTTL flag). /// Which conditions to set the values under (defaults to always). /// The flags to use for this operation. - /// The values of the specified hash fields. - RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// 0 if no fields were set, 1 if all the fields were set. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None); /// /// Returns all fields and values of the hash stored at key. diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs index 6a6c2339a..71ce7f789 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -96,38 +96,32 @@ public interface IDatabaseAsync : IRedisAsync /// Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); - /// - Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None); /// Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); - /// - Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + /// + Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None); /// Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); - /// - Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None); - /// - Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); - /// - Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None); - /// - Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); - /// - Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None); /// Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index c1041495c..b2e9a9031 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -96,38 +96,32 @@ public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => Inner.HashFieldGetAndSetExpiryAsync(key, hashField, expiry, persist, flags); - public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiryAsync(key, hashField, expiry, persist, flags); + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(key, hashField, expiry, flags); public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => Inner.HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, persist, flags); - public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, persist, flags); + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, flags); public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => Inner.HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, persist, flags); - public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, persist, flags); - - public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiryAsync(key, hashField, expiry, keepTtl, when, flags); - - public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiryAsync(key, hashField, expiry, keepTtl, when, flags); + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, flags); - public Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetLeaseAndSetExpiryAsync(key, hashField, expiry, keepTtl, when, flags); + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(key, field, value, expiry, keepTtl, when, flags); - public Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetLeaseAndSetExpiryAsync(key, hashField, expiry, keepTtl, when, flags); + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(key, field, value, expiry, when, flags); - public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, keepTtl, when, flags); - public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, keepTtl, when, flags); + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, when, flags); public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs index ff0649dd3..3f760ec96 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs @@ -93,38 +93,32 @@ public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => Inner.HashFieldGetAndSetExpiry(key, hashField, expiry, persist, flags); - public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiry(key, hashField, expiry, persist, flags); + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(key, hashField, expiry, flags); public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => Inner.HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, persist, flags); - public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, persist, flags); + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, flags); public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => Inner.HashFieldGetAndSetExpiry(key, hashFields, expiry, persist, flags); - public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiry(key, hashFields, expiry, persist, flags); - - public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiry(key, hashField, expiry, keepTtl, when, flags); - - public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiry(key, hashField, expiry, keepTtl, when, flags); + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(key, hashFields, expiry, flags); - public Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetLeaseAndSetExpiry(key, hashField, expiry, keepTtl, when, flags); + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(key, field, value, expiry, keepTtl, when, flags); - public Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetLeaseAndSetExpiry(key, hashField, expiry, keepTtl, when, flags); + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(key, field, value, expiry, when, flags); - public RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldSetAndSetExpiry(key, hashFields, expiry, keepTtl, when, flags); - public RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiry(key, hashFields, expiry, keepTtl, when, flags); + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(key, hashFields, expiry, when, flags); public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags); diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 49c644e5c..0f36c5ad0 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -1895,31 +1895,27 @@ StackExchange.Redis.ConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) - StackExchange.Redis.IConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void StackExchange.Redis.IDatabase.HashFieldGetAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.HashFieldGetAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! -StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! StackExchange.Redis.IDatabase.HashFieldGetLeaseAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? -StackExchange.Redis.IDatabase.HashFieldGetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashFieldGetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? StackExchange.Redis.IDatabase.HashFieldGetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? -StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! -StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! -StackExchange.Redis.IDatabase.HashFieldSetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? -StackExchange.Redis.IDatabase.HashFieldSetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, 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) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabaseAsync.HashFieldGetAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashFieldGetAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! -StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! -StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? 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.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, 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.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime? 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.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, 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.HashFieldSetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime? 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.HashFieldSetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, 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.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, 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.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, 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! diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 2ea93fc65..8df63d3c5 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -570,9 +570,9 @@ public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, T return ExecuteSync(msg, ResultProcessor.RedisValue); } - public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } @@ -582,9 +582,9 @@ public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, D return ExecuteSync(msg, ResultProcessor.Lease); } - public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); return ExecuteSync(msg, ResultProcessor.Lease); } @@ -596,11 +596,11 @@ public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFiel return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } - public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); if (hashFields.Length == 0) return Array.Empty(); - var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, false, flags); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -610,9 +610,9 @@ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue h return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -622,9 +622,9 @@ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue h return ExecuteAsync(msg, ResultProcessor.Lease); } - public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); return ExecuteAsync(msg, ResultProcessor.Lease); } @@ -636,15 +636,15 @@ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } - public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); - var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, false, flags); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } - private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue hashField, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct + private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue field, RedisValue value, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct { if (expiry != null && keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl"); @@ -652,33 +652,33 @@ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue hash { if (keepTtl) // Case when keepTtl is true (expiry is disregarded) { - return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, hashField); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, field, value); } if (expiry != null) // Case when expiry is not null { calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - return Message.Create(Database, flags, RedisCommand.HSETEX, key, precision, time, RedisLiterals.FIELDS, 1, hashField); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, precision, time, RedisLiterals.FIELDS, 1, field, value); } // Default case when both expiry and keepTtl are default - return Message.Create(Database, flags, RedisCommand.HSETEX, RedisLiterals.FIELDS, 1, hashField); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.FIELDS, 1, field, value); } var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; if (keepTtl) // Case when keepTtl is true (expiry is disregarded) { - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, hashField); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, field, value); } if (expiry != null) // Case when expiry is not null { calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, precision, time, RedisLiterals.FIELDS, 1, hashField); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, precision, time, RedisLiterals.FIELDS, 1, field, value); } // Only existance is specified - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.FIELDS, 1, hashField); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.FIELDS, 1, field, value); } - private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct + private Message HashFieldSetAndSetExpiryMessage(RedisKey key, HashEntry[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct { if (expiry != null && keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl"); @@ -698,7 +698,7 @@ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue[] ha arraySize += 2; // FIELDS, hashFields.Length } - arraySize += hashFields.Length; + arraySize += hashFields.Length * 2; RedisValue[] values = new RedisValue[arraySize]; int index = 0; @@ -719,86 +719,62 @@ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue[] ha } values[index++] = RedisLiterals.FIELDS; values[index++] = hashFields.Length; - Array.Copy(hashFields, 0, values, index, hashFields.Length); + for (int i = 0; i < hashFields.Length; i++) + { + values[index++] = hashFields[i].name; + values[index++] = hashFields[i].value; + } return Message.Create(Database, flags, RedisCommand.HSETEX, key, values); } - public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, expiry, CalculateExpiryValues, when, keepTtl, flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } - public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, (DateTime?)expiry, CalculateExpiryValues, when, false, flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } - public Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); - return ExecuteSync(msg, ResultProcessor.Lease); - } - - public Lease? HashFieldSetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); - return ExecuteSync(msg, ResultProcessor.Lease); - } - - public RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - if (hashFields.Length == 0) return Array.Empty(); var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); - return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + return ExecuteSync(msg, ResultProcessor.RedisValue); } - public RedisValue[] HashFieldSetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - if (hashFields.Length == 0) return Array.Empty(); - var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); - return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, when, false, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); } - public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, expiry, CalculateExpiryValues, when, keepTtl, flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, (DateTime?)expiry, CalculateExpiryValues, when, false, flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); - return ExecuteAsync(msg, ResultProcessor.Lease); - } - - public Task?> HashFieldSetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = HashFieldSetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, when, keepTtl, flags); - return ExecuteAsync(msg, ResultProcessor.Lease); - } - - public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); - var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, when, false, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); } public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => diff --git a/src/StackExchange.Redis/RedisFeatures.cs b/src/StackExchange.Redis/RedisFeatures.cs index 225516433..8ed5ddff6 100644 --- a/src/StackExchange.Redis/RedisFeatures.cs +++ b/src/StackExchange.Redis/RedisFeatures.cs @@ -44,7 +44,8 @@ namespace StackExchange.Redis v7_0_0_rc1 = new Version(6, 9, 240), // 7.0 RC1 is version 6.9.240 v7_2_0_rc1 = new Version(7, 1, 240), // 7.2 RC1 is version 7.1.240 v7_4_0_rc1 = new Version(7, 3, 240), // 7.4 RC1 is version 7.3.240 - v7_4_0_rc2 = new Version(7, 3, 241); // 7.4 RC2 is version 7.3.241 + v7_4_0_rc2 = new Version(7, 3, 241), // 7.4 RC2 is version 7.3.241 + v8_0_0_rc1 = new Version(7, 9, 240); // 8.0 RC1 is version 7.9.240 #pragma warning restore SA1310 // Field names should not contain underscore #pragma warning restore SA1311 // Static readonly fields should begin with upper-case letter diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs index e50cd0546..3196c41cf 100644 --- a/tests/StackExchange.Redis.Tests/HashFieldTests.cs +++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -19,6 +20,8 @@ public class HashFieldTests : TestBase private readonly RedisValue[] fields = new RedisValue[] { "f1", "f2" }; + private readonly RedisValue[] values = new RedisValue[] { 1, 2 }; + public HashFieldTests(ITestOutputHelper output, SharedConnectionFixture fixture) : base(output, fixture) { } @@ -302,4 +305,267 @@ public void HashFieldPersistNoField() var fieldsResult = db.HashFieldPersist(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" }); Assert.Equal(new[] { PersistResult.NoSuchField, PersistResult.NoSuchField }, fieldsResult); } + + [Fact] + public void HashFieldGetAndSetExpiry() + { + var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var hashKey = Me(); + + // testing with timespan + db.HashSet(hashKey, entries); + var fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", TimeSpan.FromHours(1)); + Assert.Equal(1, fieldResult); + var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with datetime + db.HashSet(hashKey, entries); + fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", DateTime.Now.AddMinutes(120)); + Assert.Equal(1, fieldResult); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing persist + fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", persist: true); + Assert.Equal(1, fieldResult); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.Equal(0, fieldTtl); + + // testing multiple fields with timespan + db.HashSet(hashKey, entries); + var fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, TimeSpan.FromHours(1)); + Assert.Equal(values, fieldResults); + var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing multiple fields with datetime + db.HashSet(hashKey, entries); + fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, DateTime.Now.AddMinutes(120)); + Assert.Equal(values, fieldResults); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with persist + fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, persist: true); + Assert.Equal(values, fieldResults); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.Equal(new long[] { 0, 0 }, fieldTtls); + } + + [Fact] + public async void HashFieldGetAndSetExpiryAsync() + { + var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var hashKey = Me(); + + // testing with timespan + db.HashSet(hashKey, entries); + var fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", TimeSpan.FromHours(1)); + Assert.Equal(1, fieldResult); + var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with datetime + db.HashSet(hashKey, entries); + fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", DateTime.Now.AddMinutes(120)); + Assert.Equal(1, fieldResult); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing persist + fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", persist: true); + Assert.Equal(1, fieldResult); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.Equal(0, fieldTtl); + + // testing multiple fields with timespan + db.HashSet(hashKey, entries); + var fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, TimeSpan.FromHours(1)); + Assert.Equal(values, fieldResults); + var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing multiple fields with datetime + db.HashSet(hashKey, entries); + fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, DateTime.Now.AddMinutes(120)); + Assert.Equal(values, fieldResults); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with persist + fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, persist: true); + Assert.Equal(values, fieldResults); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.Equal(new long[] { 0, 0 }, fieldTtls); + } + + [Fact] + public void HashFieldSetAndSetExpiry() + { + var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var hashKey = Me(); + + // testing with timespan + var result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, TimeSpan.FromHours(1)); + Assert.Equal(1, result); + var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with datetime + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, DateTime.Now.AddMinutes(120)); + Assert.Equal(1, result); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing with keepttl + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, keepTtl: true); + Assert.Equal(1, result); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with timespan + result = db.HashFieldSetAndSetExpiry(hashKey, entries, TimeSpan.FromHours(1)); + Assert.Equal(1, result); + var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing multiple fields with datetime + result = db.HashFieldSetAndSetExpiry(hashKey, entries, DateTime.Now.AddMinutes(120)); + Assert.Equal(1, result); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with keepttl + result = db.HashFieldSetAndSetExpiry(hashKey, entries, keepTtl: true); + Assert.Equal(1, result); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing with ExpireWhen.Exists + db.KeyDelete(hashKey); + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.Exists); + Assert.Equal(0, result); // should not set because it doesnt exist + + // testing with ExpireWhen.NotExists + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.NotExists); + Assert.Equal(1, result); // should set because it doesnt exist + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with ExpireWhen.GreaterThanCurrentExpiry + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", -1, keepTtl: true, when: When.Exists); + Assert.Equal(1, result); // should set because it exists + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + } + + [Fact] + public async Task HashFieldSetAndSetExpiryAsync() + { + var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var hashKey = Me(); + + // testing with timespan + var result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, TimeSpan.FromHours(1)); + Assert.Equal(1, result); + var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with datetime + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, DateTime.Now.AddMinutes(120)); + Assert.Equal(1, result); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing with keepttl + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, keepTtl: true); + Assert.Equal(1, result); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with timespan + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, entries, TimeSpan.FromHours(1)); + Assert.Equal(1, result); + var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing multiple fields with datetime + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, entries, DateTime.Now.AddMinutes(120)); + Assert.Equal(1, result); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with keepttl + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, entries, keepTtl: true); + Assert.Equal(1, result); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing with ExpireWhen.Exists + db.KeyDelete(hashKey); + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.Exists); + Assert.Equal(0, result); // should not set because it doesnt exist + + // testing with ExpireWhen.NotExists + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.NotExists); + Assert.Equal(1, result); // should set because it doesnt exist + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with ExpireWhen.GreaterThanCurrentExpiry + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", -1, keepTtl: true, when: When.Exists); + Assert.Equal(1, result); // should set because it exists + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + } + [Fact] + public void HashFieldGetAndDelete() + { + var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var hashKey = Me(); + + // single field + db.HashSet(hashKey, entries); + var fieldResult = db.HashFieldGetAndDelete(hashKey, "f1"); + Assert.Equal(1, fieldResult); + Assert.False(db.HashExists(hashKey, "f1")); + + // multiple fields + db.HashSet(hashKey, entries); + var fieldResults = db.HashFieldGetAndDelete(hashKey, fields); + Assert.Equal(values, fieldResults); + Assert.False(db.HashExists(hashKey, "f1")); + Assert.False(db.HashExists(hashKey, "f2")); + } + + [Fact] + public async void HashFieldGetAndDeleteAsync() + { + var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var hashKey = Me(); + + // single field + db.HashSet(hashKey, entries); + var fieldResult = await db.HashFieldGetAndDeleteAsync(hashKey, "f1"); + Assert.Equal(1, fieldResult); + Assert.False(db.HashExists(hashKey, "f1")); + + // multiple fields + db.HashSet(hashKey, entries); + var fieldResults = await db.HashFieldGetAndDeleteAsync(hashKey, fields); + Assert.Equal(fields, fieldResults); + Assert.False(db.HashExists(hashKey, "f1")); + Assert.False(db.HashExists(hashKey, "f2")); + } } From b19be94582ff9f7cde83c056311af682246c8d22 Mon Sep 17 00:00:00 2001 From: atakavci Date: Thu, 20 Mar 2025 13:11:26 +0300 Subject: [PATCH 07/19] - add new messages - add resultprocessors - fix test fails --- src/StackExchange.Redis/Message.cs | 33 ++++++++++++++ src/StackExchange.Redis/RedisDatabase.cs | 36 +++++++-------- src/StackExchange.Redis/RedisFeatures.cs | 2 +- src/StackExchange.Redis/ResultProcessor.cs | 44 +++++++++++++++++++ .../HashFieldTests.cs | 10 ++--- 5 files changed, 101 insertions(+), 24 deletions(-) diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs index b89a6b946..c6f4d0f83 100644 --- a/src/StackExchange.Redis/Message.cs +++ b/src/StackExchange.Redis/Message.cs @@ -310,6 +310,9 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, i public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) => new CommandValueValueValueValueValueMessage(db, flags, command, value0, value1, value2, value3, value4); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue[] values) => + new CommandKeyValueValueValuesMessage(db, flags, command, key, value0, value1, values); + public static Message Create( int db, CommandFlags flags, @@ -1177,6 +1180,36 @@ protected override void WriteImpl(PhysicalConnection physical) public override int ArgCount => values.Length + 1; } + private sealed class CommandKeyValueValueValuesMessage : CommandKeyBase + { + private readonly RedisValue value0; + private readonly RedisValue value1; + private readonly RedisValue[] values; + public CommandKeyValueValueValuesMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, RedisValue[] values) : base(db, flags, command, key) + { + for (int i = 0; i < values.Length; i++) + { + values[i].AssertNotNull(); + } + + value0.AssertNotNull(); + value1.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + this.values = values; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, values.Length + 3); + physical.Write(Key); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + for (int i = 0; i < values.Length; i++) physical.WriteBulkString(values[i]); + } + public override int ArgCount => values.Length + 3; + } + private sealed class CommandKeyValueValueMessage : CommandKeyBase { private readonly RedisValue value0, value1; diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 8df63d3c5..4ccc8b421 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -448,41 +448,41 @@ private T HashFieldExecute(RedisCommand cmd, RedisKey key, Custom public RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashField); - return ExecuteSync(msg, ResultProcessor.RedisValue); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField); + return ExecuteSync(msg, ResultProcessor.RedisValueFromArray); } public Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashField); - return ExecuteSync(msg, ResultProcessor.Lease); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField); + return ExecuteSync(msg, ResultProcessor.LeaseFromArray); } public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); if (hashFields.Length == 0) return Array.Empty(); - var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashFields); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, hashFields.Length, hashFields); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashField); - return ExecuteAsync(msg, ResultProcessor.RedisValue); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField); + return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray); } public Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashField); - return ExecuteAsync(msg, ResultProcessor.Lease); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField); + return ExecuteAsync(msg, ResultProcessor.LeaseFromArray); } public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); - var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, hashFields); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, hashFields.Length, hashFields); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -567,25 +567,25 @@ private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue[] ha public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) { var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); - return ExecuteSync(msg, ResultProcessor.RedisValue); + return ExecuteSync(msg, ResultProcessor.RedisValueFromArray); } public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); - return ExecuteSync(msg, ResultProcessor.RedisValue); + return ExecuteSync(msg, ResultProcessor.RedisValueFromArray); } public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) { var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); - return ExecuteSync(msg, ResultProcessor.Lease); + return ExecuteSync(msg, ResultProcessor.LeaseFromArray); } public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); - return ExecuteSync(msg, ResultProcessor.Lease); + return ExecuteSync(msg, ResultProcessor.LeaseFromArray); } public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) @@ -607,25 +607,25 @@ public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFiel public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) { var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); - return ExecuteAsync(msg, ResultProcessor.RedisValue); + return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray); } public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); - return ExecuteAsync(msg, ResultProcessor.RedisValue); + return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray); } public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) { var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); - return ExecuteAsync(msg, ResultProcessor.Lease); + return ExecuteAsync(msg, ResultProcessor.LeaseFromArray); } public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); - return ExecuteAsync(msg, ResultProcessor.Lease); + return ExecuteAsync(msg, ResultProcessor.LeaseFromArray); } public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) diff --git a/src/StackExchange.Redis/RedisFeatures.cs b/src/StackExchange.Redis/RedisFeatures.cs index 8ed5ddff6..097ce7792 100644 --- a/src/StackExchange.Redis/RedisFeatures.cs +++ b/src/StackExchange.Redis/RedisFeatures.cs @@ -45,7 +45,7 @@ namespace StackExchange.Redis v7_2_0_rc1 = new Version(7, 1, 240), // 7.2 RC1 is version 7.1.240 v7_4_0_rc1 = new Version(7, 3, 240), // 7.4 RC1 is version 7.3.240 v7_4_0_rc2 = new Version(7, 3, 241), // 7.4 RC2 is version 7.3.241 - v8_0_0_rc1 = new Version(7, 9, 240); // 8.0 RC1 is version 7.9.240 + v8_0_0_rc1 = new Version(7, 9, 0); // 8.0 RC1 is version 7.9.240 #pragma warning restore SA1310 // Field names should not contain underscore #pragma warning restore SA1311 // Static readonly fields should begin with upper-case letter diff --git a/src/StackExchange.Redis/ResultProcessor.cs b/src/StackExchange.Redis/ResultProcessor.cs index 648387b87..b80983e85 100644 --- a/src/StackExchange.Redis/ResultProcessor.cs +++ b/src/StackExchange.Redis/ResultProcessor.cs @@ -87,9 +87,15 @@ public static readonly ResultProcessor public static readonly ResultProcessor RedisValue = new RedisValueProcessor(); + public static readonly ResultProcessor + RedisValueFromArray = new RedisValueFromArrayProcessor(); + public static readonly ResultProcessor> Lease = new LeaseProcessor(); + public static readonly ResultProcessor> + LeaseFromArray = new LeaseFromArrayProcessor(); + public static readonly ResultProcessor RedisValueArray = new RedisValueArrayProcessor(); @@ -1835,6 +1841,25 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes } } + private sealed class RedisValueFromArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Array: + var items = result.GetItems(); + if (items.Length == 1) + { // treat an array of 1 like a single reply + SetResult(message, items[0].AsRedisValue()); + return true; + } + break; + } + return false; + } + } + private sealed class RoleProcessor : ResultProcessor { protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) @@ -1980,6 +2005,25 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes } } + private sealed class LeaseFromArrayProcessor : ResultProcessor> + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Array: + var items = result.GetItems(); + if (items.Length == 1) + { // treat an array of 1 like a single reply + SetResult(message, items[0].AsLease()!); + return true; + } + break; + } + return false; + } + } + private class ScriptResultProcessor : ResultProcessor { public override bool SetResult(PhysicalConnection connection, Message message, in RawResult result) diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs index 3196c41cf..f71a2f12c 100644 --- a/tests/StackExchange.Redis.Tests/HashFieldTests.cs +++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs @@ -330,7 +330,7 @@ public void HashFieldGetAndSetExpiry() fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", persist: true); Assert.Equal(1, fieldResult); fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; - Assert.Equal(0, fieldTtl); + Assert.Equal(-1, fieldTtl); // testing multiple fields with timespan db.HashSet(hashKey, entries); @@ -352,7 +352,7 @@ public void HashFieldGetAndSetExpiry() fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, persist: true); Assert.Equal(values, fieldResults); fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); - Assert.Equal(new long[] { 0, 0 }, fieldTtls); + Assert.Equal(new long[] { -1, -1 }, fieldTtls); } [Fact] @@ -379,7 +379,7 @@ public async void HashFieldGetAndSetExpiryAsync() fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", persist: true); Assert.Equal(1, fieldResult); fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; - Assert.Equal(0, fieldTtl); + Assert.Equal(-1, fieldTtl); // testing multiple fields with timespan db.HashSet(hashKey, entries); @@ -401,7 +401,7 @@ public async void HashFieldGetAndSetExpiryAsync() fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, persist: true); Assert.Equal(values, fieldResults); fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); - Assert.Equal(new long[] { 0, 0 }, fieldTtls); + Assert.Equal(new long[] { -1, -1 }, fieldTtls); } [Fact] @@ -564,7 +564,7 @@ public async void HashFieldGetAndDeleteAsync() // multiple fields db.HashSet(hashKey, entries); var fieldResults = await db.HashFieldGetAndDeleteAsync(hashKey, fields); - Assert.Equal(fields, fieldResults); + Assert.Equal(values, fieldResults); Assert.False(db.HashExists(hashKey, "f1")); Assert.False(db.HashExists(hashKey, "f2")); } From b3d2f2a123d40317404daa030e75d2fc3b7348c2 Mon Sep 17 00:00:00 2001 From: atakavci Date: Wed, 26 Mar 2025 13:09:32 +0300 Subject: [PATCH 08/19] cleanup --- .github/workflows/CI.yml | 1 - src/StackExchange.Redis/RedisFeatures.cs | 2 +- tests/StackExchange.Redis.Tests/HashFieldTests.cs | 12 ++++++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6725171c5..58856abbc 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,7 +1,6 @@ name: CI on: - workflow_dispatch: pull_request: push: branches: diff --git a/src/StackExchange.Redis/RedisFeatures.cs b/src/StackExchange.Redis/RedisFeatures.cs index 097ce7792..ffc06cf7a 100644 --- a/src/StackExchange.Redis/RedisFeatures.cs +++ b/src/StackExchange.Redis/RedisFeatures.cs @@ -45,7 +45,7 @@ namespace StackExchange.Redis v7_2_0_rc1 = new Version(7, 1, 240), // 7.2 RC1 is version 7.1.240 v7_4_0_rc1 = new Version(7, 3, 240), // 7.4 RC1 is version 7.3.240 v7_4_0_rc2 = new Version(7, 3, 241), // 7.4 RC2 is version 7.3.241 - v8_0_0_rc1 = new Version(7, 9, 0); // 8.0 RC1 is version 7.9.240 + v8_0_0_M04 = new Version(7, 9, 227); // 8.0 M04 is version 7.9.227 #pragma warning restore SA1310 // Field names should not contain underscore #pragma warning restore SA1311 // Static readonly fields should begin with upper-case letter diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs index f71a2f12c..dde3b3485 100644 --- a/tests/StackExchange.Redis.Tests/HashFieldTests.cs +++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs @@ -309,7 +309,7 @@ public void HashFieldPersistNoField() [Fact] public void HashFieldGetAndSetExpiry() { - var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); var hashKey = Me(); // testing with timespan @@ -358,7 +358,7 @@ public void HashFieldGetAndSetExpiry() [Fact] public async void HashFieldGetAndSetExpiryAsync() { - var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); var hashKey = Me(); // testing with timespan @@ -407,7 +407,7 @@ public async void HashFieldGetAndSetExpiryAsync() [Fact] public void HashFieldSetAndSetExpiry() { - var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); var hashKey = Me(); // testing with timespan @@ -470,7 +470,7 @@ public void HashFieldSetAndSetExpiry() [Fact] public async Task HashFieldSetAndSetExpiryAsync() { - var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); var hashKey = Me(); // testing with timespan @@ -532,7 +532,7 @@ public async Task HashFieldSetAndSetExpiryAsync() [Fact] public void HashFieldGetAndDelete() { - var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); var hashKey = Me(); // single field @@ -552,7 +552,7 @@ public void HashFieldGetAndDelete() [Fact] public async void HashFieldGetAndDeleteAsync() { - var db = Create(require: RedisFeatures.v8_0_0_rc1).GetDatabase(); + var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); var hashKey = Me(); // single field From e9e0cc9c7d1e02ea986647b2800002bad0833f3a Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 22 Jul 2025 13:39:00 +0100 Subject: [PATCH 09/19] fix bad merge --- tests/StackExchange.Redis.Tests/HashFieldTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs index 0065c1b21..1afb7dc07 100644 --- a/tests/StackExchange.Redis.Tests/HashFieldTests.cs +++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs @@ -18,7 +18,7 @@ public class HashFieldTests(ITestOutputHelper output, SharedConnectionFixture fi private readonly RedisValue[] fields = ["f1", "f2"]; - private readonly RedisValue[] values = new [1, 2]; + private readonly RedisValue[] values = [1, 2]; [Fact] public void HashFieldExpire() From ffb7ee972fa5aa3e22a974c5b8b2cb34ec75dd6e Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 22 Jul 2025 13:51:30 +0100 Subject: [PATCH 10/19] Update HashFieldTests.cs --- tests/StackExchange.Redis.Tests/HashFieldTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs index 1afb7dc07..2561e61ba 100644 --- a/tests/StackExchange.Redis.Tests/HashFieldTests.cs +++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs @@ -17,7 +17,7 @@ public class HashFieldTests(ITestOutputHelper output, SharedConnectionFixture fi private readonly HashEntry[] entries = [new("f1", 1), new("f2", 2)]; private readonly RedisValue[] fields = ["f1", "f2"]; - + private readonly RedisValue[] values = [1, 2]; [Fact] From ae9e389935a8ce8ba912ef063921bd18b47d2c1b Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 22 Jul 2025 13:58:59 +0100 Subject: [PATCH 11/19] Update HashFieldTests.cs --- .../HashFieldTests.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs index 2561e61ba..2bb98eb85 100644 --- a/tests/StackExchange.Redis.Tests/HashFieldTests.cs +++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs @@ -303,7 +303,8 @@ public void HashFieldPersistNoField() [Fact] public void HashFieldGetAndSetExpiry() { - var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); + using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); var hashKey = Me(); // testing with timespan @@ -350,9 +351,10 @@ public void HashFieldGetAndSetExpiry() } [Fact] - public async void HashFieldGetAndSetExpiryAsync() + public async Task HashFieldGetAndSetExpiryAsync() { - var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); var hashKey = Me(); // testing with timespan @@ -401,7 +403,8 @@ public async void HashFieldGetAndSetExpiryAsync() [Fact] public void HashFieldSetAndSetExpiry() { - var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); + using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); var hashKey = Me(); // testing with timespan @@ -464,7 +467,8 @@ public void HashFieldSetAndSetExpiry() [Fact] public async Task HashFieldSetAndSetExpiryAsync() { - var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); var hashKey = Me(); // testing with timespan @@ -526,7 +530,8 @@ public async Task HashFieldSetAndSetExpiryAsync() [Fact] public void HashFieldGetAndDelete() { - var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); + using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); var hashKey = Me(); // single field @@ -544,9 +549,10 @@ public void HashFieldGetAndDelete() } [Fact] - public async void HashFieldGetAndDeleteAsync() + public async Task HashFieldGetAndDeleteAsync() { - var db = Create(require: RedisFeatures.v8_0_0_M04).GetDatabase(); + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); var hashKey = Me(); // single field From 5721c229ceac016a0d4a2fd0db111181964b81e9 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 23 Jul 2025 10:13:59 +0100 Subject: [PATCH 12/19] move repetitive logic to ExpiryToken type (potentially also useful from other use-cases) --- .../RedisDatabase.ExpiryToken.cs | 76 ++++++ src/StackExchange.Redis/RedisDatabase.cs | 249 ++++++++---------- .../ExpiryTokenTests.cs | 117 ++++++++ 3 files changed, 299 insertions(+), 143 deletions(-) create mode 100644 src/StackExchange.Redis/RedisDatabase.ExpiryToken.cs create mode 100644 tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs diff --git a/src/StackExchange.Redis/RedisDatabase.ExpiryToken.cs b/src/StackExchange.Redis/RedisDatabase.ExpiryToken.cs new file mode 100644 index 000000000..42cfdcb18 --- /dev/null +++ b/src/StackExchange.Redis/RedisDatabase.ExpiryToken.cs @@ -0,0 +1,76 @@ +using System; + +namespace StackExchange.Redis; + +internal partial class RedisDatabase +{ + /// + /// Parses, validates and represents, for example: "EX 10", "KEEPTTL" or "". + /// + internal readonly struct ExpiryToken + { + private static readonly ExpiryToken s_Persist = new(RedisLiterals.PERSIST), s_KeepTtl = new(RedisLiterals.KEEPTTL), s_Null = new(RedisValue.Null); + + public RedisValue Operand { get; } + public long Value { get; } + public int Tokens => Value == long.MinValue ? (Operand.IsNull ? 0 : 1) : 2; + public bool HasValue => Value != long.MinValue; + public bool HasOperand => !Operand.IsNull; + + public static ExpiryToken Persist(TimeSpan? expiry, bool persist) + { + if (expiry.HasValue) + { + if (persist) throw new ArgumentException("Cannot specify both expiry and persist", nameof(persist)); + return new(expiry.GetValueOrDefault()); // EX 10 + } + + return persist ? s_Persist : s_Null; // PERSIST (or nothing) + } + + public static ExpiryToken KeepTtl(TimeSpan? expiry, bool keepTtl) + { + if (expiry.HasValue) + { + if (keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl", nameof(keepTtl)); + return new(expiry.GetValueOrDefault()); // EX 10 + } + + return keepTtl ? s_KeepTtl : s_Null; // KEEPTTL (or nothing) + } + + private ExpiryToken(RedisValue operand, long value = long.MinValue) + { + Operand = operand; + Value = value; + } + + public ExpiryToken(TimeSpan expiry) + { + long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond; + var useSeconds = milliseconds % 1000 == 0; + + Operand = useSeconds ? RedisLiterals.EX : RedisLiterals.PX; + Value = useSeconds ? (milliseconds / 1000) : milliseconds; + } + + public ExpiryToken(DateTime expiry) + { + long milliseconds = GetUnixTimeMilliseconds(expiry); + var useSeconds = milliseconds % 1000 == 0; + + Operand = useSeconds ? RedisLiterals.EXAT : RedisLiterals.PXAT; + Value = useSeconds ? (milliseconds / 1000) : milliseconds; + } + + public override string ToString() => Tokens switch + { + 2 => $"{Operand} {Value}", + 1 => Operand.ToString(), + _ => "", + }; + + public override int GetHashCode() => throw new NotSupportedException(); + public override bool Equals(object? obj) => throw new NotSupportedException(); + } +} diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index dc3e28d9d..eedaebdf7 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -8,7 +8,7 @@ namespace StackExchange.Redis { - internal class RedisDatabase : RedisBase, IDatabase + internal partial class RedisDatabase : RedisBase, IDatabase { internal RedisDatabase(ConnectionMultiplexer multiplexer, int db, object? asyncState) : base(multiplexer, asyncState) @@ -396,7 +396,7 @@ public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, Tim public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) { - long milliseconds = GetMillisecondsUntil(expiry); + long milliseconds = GetUnixTimeMilliseconds(expiry); return HashFieldExpireExecute(key, milliseconds, when, PickExpireAtCommandByPrecision, SyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields); } @@ -408,7 +408,7 @@ public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hash public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) { - long milliseconds = GetMillisecondsUntil(expiry); + long milliseconds = GetUnixTimeMilliseconds(expiry); return HashFieldExpireExecute(key, milliseconds, when, PickExpireAtCommandByPrecision, AsyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields); } @@ -487,105 +487,75 @@ public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } - private delegate void CalculateExpiryArgs(T expiry, out RedisValue precision, out RedisValue time); - - private void CalculateExpiryValues(TimeSpan expiry, out RedisValue precision, out RedisValue time) - { - long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond; - var useSeconds = milliseconds % 1000 == 0; - precision = useSeconds ? RedisLiterals.EX : RedisLiterals.PX; - time = useSeconds ? (milliseconds / 1000) : milliseconds; - } - - private void CalculateExpiryValues(DateTime expiry, out RedisValue precision, out RedisValue time) - { - long milliseconds = GetMillisecondsUntil(expiry); - var useSeconds = milliseconds % 1000 == 0; - precision = useSeconds ? RedisLiterals.EXAT : RedisLiterals.PXAT; - time = useSeconds ? (milliseconds / 1000) : milliseconds; - } - - private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue hashField, T? expiry, CalculateExpiryArgs calculateExpiryArgs, bool persist, CommandFlags flags) where T : struct - { - if (expiry != null && persist) throw new ArgumentException("Cannot specify both expiry and persist"); - - if (persist) // Case when persist is true (expiry is disregarded) + private Message HashFieldGetAndSetExpiryMessage(in RedisKey key, in RedisValue hashField, ExpiryToken expiry, CommandFlags flags) => + expiry.Tokens switch { - return Message.Create(Database, flags, RedisCommand.HGETEX, key, RedisLiterals.PERSIST, RedisLiterals.FIELDS, 1, hashField); - } - - if (expiry != null) // Check if expiry is not null - { - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - return Message.Create(Database, flags, RedisCommand.HGETEX, key, precision, time, RedisLiterals.FIELDS, 1, hashField); - } - - // Default case when neither expiry nor persist are set - return Message.Create(Database, flags, RedisCommand.HGETEX, RedisLiterals.FIELDS, 1, hashField); - } + // expiry, for example EX 10 + 2 => Message.Create(Database, flags, RedisCommand.HGETEX, key, expiry.Operand, expiry.Value, RedisLiterals.FIELDS, 1, hashField), + // keyword only, for example PERSIST + 1 => Message.Create(Database, flags, RedisCommand.HGETEX, key, expiry.Operand, RedisLiterals.FIELDS, 1, hashField), + // default case when neither expiry nor persist are set + _ => Message.Create(Database, flags, RedisCommand.HGETEX, key, RedisLiterals.FIELDS, 1, hashField), + }; - private Message HashFieldGetAndSetExpiryMessage(RedisKey key, RedisValue[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, bool persist, CommandFlags flags) where T : struct + private Message HashFieldGetAndSetExpiryMessage(in RedisKey key, RedisValue[] hashFields, ExpiryToken expiry, CommandFlags flags) { - if (expiry != null && persist) throw new ArgumentException("Cannot specify both expiry and persist"); - - // Calculate the total size of the array based on conditions - int arraySize = 0; - if (persist) // Case when persist is true (expiry is disregarded) + if (hashFields is null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 1) { - arraySize = 3; // PERSIST, FIELDS, hashFields.Length - } - else if (expiry != null) // Case when expiry is not null - { - arraySize = 4; // precision, time, FIELDS, hashFields.Length - } - else // Default case when both expiry and persist are default - { - arraySize = 2; // FIELDS, hashFields.Length + return HashFieldGetAndSetExpiryMessage(key, in hashFields[0], expiry, flags); } - // Create an array to hold the values (including hashFields) - RedisValue[] values = new RedisValue[arraySize + hashFields.Length]; + // precision, time, FIELDS, hashFields.Length + int extraTokens = expiry.Tokens + 2; + + RedisValue[] values = new RedisValue[expiry.Tokens + 2 + hashFields.Length]; int index = 0; - // Add PERSIST or expiry values, or just FIELDS - if (persist) // Case when persist is true (expiry is disregarded) - { - values[index++] = RedisLiterals.PERSIST; - } - else if (expiry != null) // Check if expiry is not null + // add PERSIST or expiry values + switch (expiry.Tokens) { - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - values[index++] = precision; - values[index++] = time; + case 2: + values[index++] = expiry.Operand; + values[index++] = expiry.Value; + break; + case 1: + values[index++] = expiry.Operand; + break; } + // add the fields values[index++] = RedisLiterals.FIELDS; values[index++] = hashFields.Length; + // check we've added everything we expected to + Debug.Assert(index == extraTokens + hashFields.Length); + // Add hash fields to the array - Array.Copy(hashFields, 0, values, index, hashFields.Length); + hashFields.AsSpan().CopyTo(values.AsSpan(index)); + return Message.Create(Database, flags, RedisCommand.HGETEX, key, values); } public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, ExpiryToken.Persist(expiry, persist), flags); return ExecuteSync(msg, ResultProcessor.RedisValueFromArray); } public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, new(expiry), flags); return ExecuteSync(msg, ResultProcessor.RedisValueFromArray); } public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, ExpiryToken.Persist(expiry, persist), flags); return ExecuteSync(msg, ResultProcessor.LeaseFromArray); } public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, new(expiry), flags); return ExecuteSync(msg, ResultProcessor.LeaseFromArray); } @@ -593,7 +563,7 @@ public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFiel { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); if (hashFields.Length == 0) return Array.Empty(); - var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, ExpiryToken.Persist(expiry, persist), flags); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -601,31 +571,31 @@ public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFiel { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); if (hashFields.Length == 0) return Array.Empty(); - var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, false, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, new(expiry), flags); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, ExpiryToken.Persist(expiry, persist), flags); return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray); } public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, new(expiry), flags); return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray); } public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, ExpiryToken.Persist(expiry, persist), flags); return ExecuteAsync(msg, ResultProcessor.LeaseFromArray); } public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldGetAndSetExpiryMessage(key, hashField, (DateTime?)expiry, CalculateExpiryValues, false, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, new(expiry), flags); return ExecuteAsync(msg, ResultProcessor.LeaseFromArray); } @@ -633,7 +603,7 @@ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); - var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, persist, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, ExpiryToken.Persist(expiry, persist), flags); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -641,82 +611,74 @@ public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); - var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, false, flags); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, new(expiry), flags); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } - private Message HashFieldSetAndSetExpiryMessage(RedisKey key, RedisValue field, RedisValue value, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct + private Message HashFieldSetAndSetExpiryMessage(in RedisKey key, in RedisValue field, in RedisValue value, ExpiryToken expiry, When when, CommandFlags flags) { - if (expiry != null && keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl"); - if (when == When.Always) { - if (keepTtl) // Case when keepTtl is true (expiry is disregarded) - { - return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, field, value); - } - - if (expiry != null) // Case when expiry is not null + return expiry.Tokens switch { - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - return Message.Create(Database, flags, RedisCommand.HSETEX, key, precision, time, RedisLiterals.FIELDS, 1, field, value); - } - // Default case when both expiry and keepTtl are default - return Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.FIELDS, 1, field, value); - } - - var existance = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - if (keepTtl) // Case when keepTtl is true (expiry is disregarded) - { - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.KEEPTTL, RedisLiterals.FIELDS, 1, field, value); + 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), + _ => Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.FIELDS, 1, field, value), + }; } - if (expiry != null) // Case when expiry is not null + else { - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, precision, time, RedisLiterals.FIELDS, 1, field, value); + // we need an extra token + var existance = when switch + { + When.Exists => RedisLiterals.FXX, + When.NotExists => RedisLiterals.FNX, + _ => throw new ArgumentOutOfRangeException(nameof(when)), + }; + + return expiry.Tokens 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), + _ => Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.FIELDS, 1, field, value), + }; } - // Only existance is specified - return Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.FIELDS, 1, field, value); } - private Message HashFieldSetAndSetExpiryMessage(RedisKey key, HashEntry[] hashFields, T? expiry, CalculateExpiryArgs calculateExpiryArgs, When when, bool keepTtl, CommandFlags flags) where T : struct + private Message HashFieldSetAndSetExpiryMessage(in RedisKey key, HashEntry[] hashFields, ExpiryToken expiry, When when, CommandFlags flags) { - if (expiry != null && keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl"); - - // Determine the base array size - int arraySize = when == When.Always ? 0 : 1; - - if (keepTtl) + if (hashFields.Length == 1) { - arraySize += 3; // KEEPTTL, FIELDS, hashFields.Length - } - else if (expiry != null) - { - arraySize += 4; // precision, time, FIELDS, hashFields.Length - } - else - { - arraySize += 2; // FIELDS, hashFields.Length + var field = hashFields[0]; + 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} + RedisValue[] values = new RedisValue[(hashFields.Length * 2) + extraTokens]; - arraySize += hashFields.Length * 2; - RedisValue[] values = new RedisValue[arraySize]; int index = 0; - - if (when != When.Always) - { - values[index++] = when == When.Exists ? RedisLiterals.FXX : RedisLiterals.FNX; - } - - if (keepTtl) // Case when keepTtl is true (expiry is disregarded) + switch (when) { - values[index++] = RedisLiterals.KEEPTTL; + case When.Always: + break; + case When.Exists: + values[index++] = RedisLiterals.FXX; + break; + case When.NotExists: + values[index++] = RedisLiterals.FNX; + break; + default: + throw new ArgumentOutOfRangeException(nameof(when)); } - else if (expiry != null) // Case when expiry is not null + switch (expiry.Tokens) { - calculateExpiryArgs((T)expiry!, out RedisValue precision, out RedisValue time); - values[index++] = precision; - values[index++] = time; + case 2: + values[index++] = expiry.Operand; + values[index++] = expiry.Value; + break; + case 1: + values[index++] = expiry.Operand; + break; } values[index++] = RedisLiterals.FIELDS; values[index++] = hashFields.Length; @@ -725,56 +687,57 @@ private Message HashFieldSetAndSetExpiryMessage(RedisKey key, HashEntry[] has values[index++] = hashFields[i].name; values[index++] = hashFields[i].value; } + Debug.Assert(index == values.Length); return Message.Create(Database, flags, RedisCommand.HSETEX, key, values); } public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldSetAndSetExpiryMessage(key, field, value, expiry, CalculateExpiryValues, when, keepTtl, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, ExpiryToken.KeepTtl(expiry, keepTtl), when, flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldSetAndSetExpiryMessage(key, field, value, (DateTime?)expiry, CalculateExpiryValues, when, false, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, new(expiry), when, flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, ExpiryToken.KeepTtl(expiry, keepTtl), when, flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, when, false, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, new(expiry), when, flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldSetAndSetExpiryMessage(key, field, value, expiry, CalculateExpiryValues, when, keepTtl, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, ExpiryToken.KeepTtl(expiry, keepTtl), when, flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) { - var msg = HashFieldSetAndSetExpiryMessage(key, field, value, (DateTime?)expiry, CalculateExpiryValues, when, false, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, new(expiry), when, flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, expiry, CalculateExpiryValues, when, keepTtl, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, ExpiryToken.KeepTtl(expiry, keepTtl), when, flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, (DateTime?)expiry, CalculateExpiryValues, when, false, flags); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, new(expiry), when, flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -3805,7 +3768,7 @@ public Task StringSetRangeAsync(RedisKey key, long offset, RedisValu return ExecuteAsync(msg, ResultProcessor.RedisValue); } - private long GetMillisecondsUntil(DateTime when) => when.Kind switch + private static long GetUnixTimeMilliseconds(DateTime when) => when.Kind switch { DateTimeKind.Local or DateTimeKind.Utc => (when.ToUniversalTime() - RedisBase.UnixEpoch).Ticks / TimeSpan.TicksPerMillisecond, _ => throw new ArgumentException("Expiry time must be either Utc or Local", nameof(when)), @@ -3849,7 +3812,7 @@ private Message GetExpiryMessage(in RedisKey key, CommandFlags flags, DateTime? }; } - long milliseconds = GetMillisecondsUntil(expiry.Value); + long milliseconds = GetUnixTimeMilliseconds(expiry.Value); return GetExpiryMessage(key, RedisCommand.PEXPIREAT, RedisCommand.EXPIREAT, milliseconds, when, flags, out server); } @@ -5008,7 +4971,7 @@ private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destina private Message GetStringGetExMessage(in RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) => expiry == DateTime.MaxValue ? Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PERSIST) - : Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PXAT, GetMillisecondsUntil(expiry)); + : Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PXAT, GetUnixTimeMilliseconds(expiry)); private Message GetStringGetWithExpiryMessage(RedisKey key, CommandFlags flags, out ResultProcessor processor, out ServerEndPoint? server) { diff --git a/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs b/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs new file mode 100644 index 000000000..3f0d39f28 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs @@ -0,0 +1,117 @@ +using System; +using Xunit; +using static StackExchange.Redis.RedisDatabase; +using static StackExchange.Redis.RedisDatabase.ExpiryToken; +namespace StackExchange.Redis.Tests; + +public class ExpiryTokenTests // pure tests, no DB +{ + [Fact] + public void Persist_Seconds() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5000); + var ex = Persist(time, false); + Assert.Equal(2, ex.Tokens); + Assert.Equal("EX 5", ex.ToString()); + } + + [Fact] + public void Persist_Milliseconds() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5001); + var ex = Persist(time, false); + Assert.Equal(2, ex.Tokens); + Assert.Equal("PX 5001", ex.ToString()); + } + + [Fact] + public void Persist_None_False() + { + TimeSpan? time = null; + var ex = Persist(time, false); + Assert.Equal(0, ex.Tokens); + Assert.Equal("", ex.ToString()); + } + + [Fact] + public void Persist_None_True() + { + TimeSpan? time = null; + var ex = Persist(time, true); + Assert.Equal(1, ex.Tokens); + Assert.Equal("PERSIST", ex.ToString()); + } + + [Fact] + public void Persist_Both() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5000); + var ex = Assert.Throws(() => Persist(time, true)); + Assert.Equal("persist", ex.ParamName); + Assert.StartsWith("Cannot specify both expiry and persist", ex.Message); + } + + [Fact] + public void KeepTtl_Seconds() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5000); + var ex = KeepTtl(time, false); + Assert.Equal(2, ex.Tokens); + Assert.Equal("EX 5", ex.ToString()); + } + + [Fact] + public void KeepTtl_Milliseconds() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5001); + var ex = KeepTtl(time, false); + Assert.Equal(2, ex.Tokens); + Assert.Equal("PX 5001", ex.ToString()); + } + + [Fact] + public void KeepTtl_None_False() + { + TimeSpan? time = null; + var ex = KeepTtl(time, false); + Assert.Equal(0, ex.Tokens); + Assert.Equal("", ex.ToString()); + } + + [Fact] + public void KeepTtl_None_True() + { + TimeSpan? time = null; + var ex = KeepTtl(time, true); + Assert.Equal(1, ex.Tokens); + Assert.Equal("KEEPTTL", ex.ToString()); + } + + [Fact] + public void KeepTtl_Both() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5000); + var ex = Assert.Throws(() => KeepTtl(time, true)); + Assert.Equal("keepTtl", ex.ParamName); + Assert.StartsWith("Cannot specify both expiry and keepTtl", ex.Message); + } + + [Fact] + public void DateTime_Seconds() + { + var when = new DateTime(2025, 7, 23, 10, 4, 14, DateTimeKind.Utc); + var ex = new ExpiryToken(when); + Assert.Equal(2, ex.Tokens); + Assert.Equal("EXAT 1753265054", ex.ToString()); + } + + [Fact] + public void DateTime_Milliseconds() + { + var when = new DateTime(2025, 7, 23, 10, 4, 14, DateTimeKind.Utc); + when = when.AddMilliseconds(14); + var ex = new ExpiryToken(when); + Assert.Equal(2, ex.Tokens); + Assert.Equal("PXAT 1753265054014", ex.ToString()); + } +} From 1817d93d82228b9d8f0867513e4465984c5ed9be Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 23 Jul 2025 10:36:27 +0100 Subject: [PATCH 13/19] fix missing ToInner usage in key-prefixed wrapper; in addition to the new code, this includes pre-existing bugs: - SetIntersectionLength - SortedSetCombine - SortedSetCombineWithScores - SortedSetIntersectionLength --- .../KeyspaceIsolation/KeyPrefixed.cs | 34 +++++++++---------- .../KeyspaceIsolation/KeyPrefixedDatabase.cs | 34 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index 11cfe412e..06c5359eb 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -85,43 +85,43 @@ public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFla Inner.HashExistsAsync(ToInner(key), hashField, flags); public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndDeleteAsync(key, hashField, flags); + Inner.HashFieldGetAndDeleteAsync(ToInner(key), hashField, flags); public Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetLeaseAndDeleteAsync(key, hashField, flags); + Inner.HashFieldGetLeaseAndDeleteAsync(ToInner(key), hashField, flags); public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndDeleteAsync(key, hashFields, flags); + Inner.HashFieldGetAndDeleteAsync(ToInner(key), hashFields, flags); public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiryAsync(key, hashField, expiry, persist, flags); + Inner.HashFieldGetAndSetExpiryAsync(ToInner(key), hashField, expiry, persist, flags); public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiryAsync(key, hashField, expiry, flags); + Inner.HashFieldGetAndSetExpiryAsync(ToInner(key), hashField, expiry, flags); public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, persist, flags); + Inner.HashFieldGetLeaseAndSetExpiryAsync(ToInner(key), hashField, expiry, persist, flags); public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, flags); + Inner.HashFieldGetLeaseAndSetExpiryAsync(ToInner(key), hashField, expiry, flags); public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, persist, flags); + Inner.HashFieldGetAndSetExpiryAsync(ToInner(key), hashFields, expiry, persist, flags); public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, flags); + Inner.HashFieldGetAndSetExpiryAsync(ToInner(key), hashFields, expiry, flags); public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiryAsync(key, field, value, expiry, keepTtl, when, flags); + Inner.HashFieldSetAndSetExpiryAsync(ToInner(key), field, value, expiry, keepTtl, when, flags); public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiryAsync(key, field, value, expiry, when, flags); + Inner.HashFieldSetAndSetExpiryAsync(ToInner(key), field, value, expiry, when, flags); public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, keepTtl, when, flags); + Inner.HashFieldSetAndSetExpiryAsync(ToInner(key), hashFields, expiry, keepTtl, when, flags); public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, when, flags); + Inner.HashFieldSetAndSetExpiryAsync(ToInner(key), hashFields, expiry, when, flags); public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags); @@ -433,7 +433,7 @@ public Task SetContainsAsync(RedisKey key, RedisValue[] values, CommandF Inner.SetContainsAsync(ToInner(key), values, flags); public Task SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) => - Inner.SetIntersectionLengthAsync(keys, limit, flags); + Inner.SetIntersectionLengthAsync(ToInner(keys), limit, flags); public Task SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => Inner.SetLengthAsync(ToInner(key), flags); @@ -489,10 +489,10 @@ public Task SortedSetAddAsync(RedisKey key, RedisValue member, double scor public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, SortedSetWhen updateWhen = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.SortedSetAddAsync(ToInner(key), member, score, updateWhen, flags); public Task SortedSetCombineAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => - Inner.SortedSetCombineAsync(operation, keys, weights, aggregate, flags); + Inner.SortedSetCombineAsync(operation, ToInner(keys), weights, aggregate, flags); public Task SortedSetCombineWithScoresAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => - Inner.SortedSetCombineWithScoresAsync(operation, keys, weights, aggregate, flags); + Inner.SortedSetCombineWithScoresAsync(operation, ToInner(keys), weights, aggregate, flags); public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags); @@ -507,7 +507,7 @@ public Task SortedSetIncrementAsync(RedisKey key, RedisValue member, dou Inner.SortedSetIncrementAsync(ToInner(key), member, value, flags); public Task SortedSetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) => - Inner.SortedSetIntersectionLengthAsync(keys, limit, flags); + Inner.SortedSetIntersectionLengthAsync(ToInner(keys), limit, flags); public Task SortedSetLengthAsync(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => Inner.SortedSetLengthAsync(ToInner(key), min, max, exclude, flags); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs index d7bbdc68b..48df1e4db 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs @@ -82,43 +82,43 @@ public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = Inner.HashExists(ToInner(key), hashField, flags); public RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndDelete(key, hashField, flags); + Inner.HashFieldGetAndDelete(ToInner(key), hashField, flags); public Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetLeaseAndDelete(key, hashField, flags); + Inner.HashFieldGetLeaseAndDelete(ToInner(key), hashField, flags); public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndDelete(key, hashFields, flags); + Inner.HashFieldGetAndDelete(ToInner(key), hashFields, flags); public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiry(key, hashField, expiry, persist, flags); + Inner.HashFieldGetAndSetExpiry(ToInner(key), hashField, expiry, persist, flags); public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiry(key, hashField, expiry, flags); + Inner.HashFieldGetAndSetExpiry(ToInner(key), hashField, expiry, flags); public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, persist, flags); + Inner.HashFieldGetLeaseAndSetExpiry(ToInner(key), hashField, expiry, persist, flags); public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, flags); + Inner.HashFieldGetLeaseAndSetExpiry(ToInner(key), hashField, expiry, flags); public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiry(key, hashFields, expiry, persist, flags); + Inner.HashFieldGetAndSetExpiry(ToInner(key), hashFields, expiry, persist, flags); public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldGetAndSetExpiry(key, hashFields, expiry, flags); + Inner.HashFieldGetAndSetExpiry(ToInner(key), hashFields, expiry, flags); public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiry(key, field, value, expiry, keepTtl, when, flags); + Inner.HashFieldSetAndSetExpiry(ToInner(key), field, value, expiry, keepTtl, when, flags); public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiry(key, field, value, expiry, when, flags); + Inner.HashFieldSetAndSetExpiry(ToInner(key), field, value, expiry, when, flags); public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiry(key, hashFields, expiry, keepTtl, when, flags); + Inner.HashFieldSetAndSetExpiry(ToInner(key), hashFields, expiry, keepTtl, when, flags); public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => - Inner.HashFieldSetAndSetExpiry(key, hashFields, expiry, when, flags); + Inner.HashFieldSetAndSetExpiry(ToInner(key), hashFields, expiry, when, flags); public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags); @@ -420,7 +420,7 @@ public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags Inner.SetContains(ToInner(key), values, flags); public long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) => - Inner.SetIntersectionLength(keys, limit, flags); + Inner.SetIntersectionLength(ToInner(keys), limit, flags); public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None) => Inner.SetLength(ToInner(key), flags); @@ -474,10 +474,10 @@ public bool SortedSetAdd(RedisKey key, RedisValue member, double score, SortedSe Inner.SortedSetAdd(ToInner(key), member, score, when, flags); public RedisValue[] SortedSetCombine(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => - Inner.SortedSetCombine(operation, keys, weights, aggregate, flags); + Inner.SortedSetCombine(operation, ToInner(keys), weights, aggregate, flags); public SortedSetEntry[] SortedSetCombineWithScores(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => - Inner.SortedSetCombineWithScores(operation, keys, weights, aggregate, flags); + Inner.SortedSetCombineWithScores(operation, ToInner(keys), weights, aggregate, flags); public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags); @@ -492,7 +492,7 @@ public double SortedSetIncrement(RedisKey key, RedisValue member, double value, Inner.SortedSetIncrement(ToInner(key), member, value, flags); public long SortedSetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) => - Inner.SortedSetIntersectionLength(keys, limit, flags); + Inner.SortedSetIntersectionLength(ToInner(keys), limit, flags); public long SortedSetLength(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => Inner.SortedSetLength(ToInner(key), min, max, exclude, flags); From b81ad627059860984d127420bf71510aca297c24 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 23 Jul 2025 11:26:03 +0100 Subject: [PATCH 14/19] simplify and fix key prefix tests --- .../KeyPrefixedDatabaseTests.cs | 83 ++++++++----------- .../KeyPrefixedTests.cs | 73 ++++++---------- 2 files changed, 63 insertions(+), 93 deletions(-) diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs index 612ca182b..d830f4f8b 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Net; using System.Text; @@ -18,6 +19,14 @@ public sealed class KeyPrefixedDatabaseTests private readonly IDatabase mock; private readonly IDatabase prefixed; + internal static RedisKey[] IsKeys(params RedisKey[] expected) => IsRaw(expected); + internal static RedisValue[] IsValues(params RedisValue[] expected) => IsRaw(expected); + private static T[] IsRaw(T[] expected) + { + Expression> lambda = actual => actual.Length == expected.Length && expected.SequenceEqual(actual); + return Arg.Is(lambda); + } + public KeyPrefixedDatabaseTests() { mock = Substitute.For(); @@ -237,10 +246,8 @@ public void HyperLogLogMerge_1() [Fact] public void HyperLogLogMerge_2() { - RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - prefixed.HyperLogLogMerge("destination", keys, CommandFlags.None); - mock.Received().HyperLogLogMerge("prefix:destination", Arg.Is(valid), CommandFlags.None); + prefixed.HyperLogLogMerge("destination", ["a", "b"], CommandFlags.None); + mock.Received().HyperLogLogMerge("prefix:destination", IsKeys(["prefix:a", "prefix:b"]), CommandFlags.None); } [Fact] @@ -267,10 +274,8 @@ public void KeyDelete_1() [Fact] public void KeyDelete_2() { - RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - prefixed.KeyDelete(keys, CommandFlags.None); - mock.Received().KeyDelete(Arg.Is(valid), CommandFlags.None); + prefixed.KeyDelete(["a", "b"], CommandFlags.None); + mock.Received().KeyDelete(IsKeys(["prefix:a", "prefix:b"]), CommandFlags.None); } [Fact] @@ -594,9 +599,8 @@ public void ScriptEvaluate_1() byte[] hash = Array.Empty(); RedisValue[] values = Array.Empty(); RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; prefixed.ScriptEvaluate(hash, keys, values, CommandFlags.None); - mock.Received().ScriptEvaluate(hash, Arg.Is(valid), values, CommandFlags.None); + mock.Received().ScriptEvaluate(hash, IsKeys(["prefix:a", "prefix:b"]), values, CommandFlags.None); } [Fact] @@ -604,9 +608,8 @@ public void ScriptEvaluate_2() { RedisValue[] values = Array.Empty(); RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; prefixed.ScriptEvaluate(script: "script", keys: keys, values: values, flags: CommandFlags.None); - mock.Received().ScriptEvaluate(script: "script", keys: Arg.Is(valid), values: values, flags: CommandFlags.None); + mock.Received().ScriptEvaluate(script: "script", keys: IsKeys(["prefix:a", "prefix:b"]), values: values, flags: CommandFlags.None); } [Fact] @@ -635,9 +638,8 @@ public void SetCombine_1() public void SetCombine_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; prefixed.SetCombine(SetOperation.Intersect, keys, CommandFlags.None); - mock.Received().SetCombine(SetOperation.Intersect, Arg.Is(valid), CommandFlags.None); + mock.Received().SetCombine(SetOperation.Intersect, IsKeys(["prefix:a", "prefix:b"]), CommandFlags.None); } [Fact] @@ -651,9 +653,8 @@ public void SetCombineAndStore_1() public void SetCombineAndStore_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; prefixed.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.None); - mock.Received().SetCombineAndStore(SetOperation.Intersect, "prefix:destination", Arg.Is(valid), CommandFlags.None); + mock.Received().SetCombineAndStore(SetOperation.Intersect, "prefix:destination", IsKeys(["prefix:a", "prefix:b"]), CommandFlags.None); } [Fact] @@ -674,9 +675,8 @@ public void SetContains_2() [Fact] public void SetIntersectionLength() { - var keys = new RedisKey[] { "key1", "key2" }; - prefixed.SetIntersectionLength(keys); - mock.Received().SetIntersectionLength(keys, 0, CommandFlags.None); + prefixed.SetIntersectionLength(["key1", "key2"]); + mock.Received().SetIntersectionLength(IsKeys(["prefix:key1", "prefix:key2"]), 0, CommandFlags.None); } [Fact] @@ -764,26 +764,24 @@ public void SetScan_Full() public void Sort() { RedisValue[] get = ["a", "#"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; prefixed.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.None); prefixed.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.None); - mock.Received().Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", Arg.Is(valid), CommandFlags.None); - mock.Received().Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", Arg.Is(valid), CommandFlags.None); + mock.Received().Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", IsValues(["prefix:a", "#"]), CommandFlags.None); + mock.Received().Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", IsValues(["prefix:a", "#"]), CommandFlags.None); } [Fact] public void SortAndStore() { RedisValue[] get = ["a", "#"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; prefixed.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.None); prefixed.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.None); - mock.Received().SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", Arg.Is(valid), CommandFlags.None); - mock.Received().SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", Arg.Is(valid), CommandFlags.None); + mock.Received().SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", IsValues(["prefix:a", "#"]), CommandFlags.None); + mock.Received().SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", IsValues(["prefix:a", "#"]), CommandFlags.None); } [Fact] @@ -813,16 +811,15 @@ public void SortedSetAdd_3() public void SortedSetCombine() { RedisKey[] keys = ["a", "b"]; - prefixed.SortedSetCombine(SetOperation.Intersect, keys); - mock.Received().SortedSetCombine(SetOperation.Intersect, keys, null, Aggregate.Sum, CommandFlags.None); + prefixed.SortedSetCombine(SetOperation.Intersect, ["a", "b"]); + mock.Received().SortedSetCombine(SetOperation.Intersect, IsKeys(["prefix:a", "prefix:b"]), null, Aggregate.Sum, CommandFlags.None); } [Fact] public void SortedSetCombineWithScores() { - RedisKey[] keys = ["a", "b"]; - prefixed.SortedSetCombineWithScores(SetOperation.Intersect, keys); - mock.Received().SortedSetCombineWithScores(SetOperation.Intersect, keys, null, Aggregate.Sum, CommandFlags.None); + prefixed.SortedSetCombineWithScores(SetOperation.Intersect, ["a", "b"]); + mock.Received().SortedSetCombineWithScores(SetOperation.Intersect, IsKeys("prefix:a", "prefix:b"), null, Aggregate.Sum, CommandFlags.None); } [Fact] @@ -836,9 +833,8 @@ public void SortedSetCombineAndStore_1() public void SortedSetCombineAndStore_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; prefixed.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.None); - mock.Received().SetCombineAndStore(SetOperation.Intersect, "prefix:destination", Arg.Is(valid), CommandFlags.None); + mock.Received().SetCombineAndStore(SetOperation.Intersect, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] @@ -858,9 +854,8 @@ public void SortedSetIncrement() [Fact] public void SortedSetIntersectionLength() { - RedisKey[] keys = ["a", "b"]; - prefixed.SortedSetIntersectionLength(keys, 1, CommandFlags.None); - mock.Received().SortedSetIntersectionLength(keys, 1, CommandFlags.None); + prefixed.SortedSetIntersectionLength(["a", "b"], 1, CommandFlags.None); + mock.Received().SortedSetIntersectionLength(IsKeys("prefix:a", "prefix:b"), 1, CommandFlags.None); } [Fact] @@ -1255,45 +1250,40 @@ public void StringBitOperation_1() public void StringBitOperation_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; prefixed.StringBitOperation(Bitwise.Xor, "destination", keys, CommandFlags.None); - mock.Received().StringBitOperation(Bitwise.Xor, "prefix:destination", Arg.Is(valid), CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.Xor, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] public void StringBitOperation_Diff() { RedisKey[] keys = ["x", "y1", "y2"]; - Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; prefixed.StringBitOperation(Bitwise.Diff, "destination", keys, CommandFlags.None); - mock.Received().StringBitOperation(Bitwise.Diff, "prefix:destination", Arg.Is(valid), CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.Diff, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); } [Fact] public void StringBitOperation_Diff1() { RedisKey[] keys = ["x", "y1", "y2"]; - Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; prefixed.StringBitOperation(Bitwise.Diff1, "destination", keys, CommandFlags.None); - mock.Received().StringBitOperation(Bitwise.Diff1, "prefix:destination", Arg.Is(valid), CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.Diff1, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); } [Fact] public void StringBitOperation_AndOr() { RedisKey[] keys = ["x", "y1", "y2"]; - Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; prefixed.StringBitOperation(Bitwise.AndOr, "destination", keys, CommandFlags.None); - mock.Received().StringBitOperation(Bitwise.AndOr, "prefix:destination", Arg.Is(valid), CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.AndOr, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); } [Fact] public void StringBitOperation_One() { RedisKey[] keys = ["a", "b", "c"]; - Expression> valid = _ => _.Length == 3 && _[0] == "prefix:a" && _[1] == "prefix:b" && _[2] == "prefix:c"; prefixed.StringBitOperation(Bitwise.One, "destination", keys, CommandFlags.None); - mock.Received().StringBitOperation(Bitwise.One, "prefix:destination", Arg.Is(valid), CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.One, "prefix:destination", IsKeys("prefix:a", "prefix:b", "prefix:c"), CommandFlags.None); } [Fact] @@ -1335,9 +1325,8 @@ public void StringGet_1() public void StringGet_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; prefixed.StringGet(keys, CommandFlags.None); - mock.Received().StringGet(Arg.Is(valid), CommandFlags.None); + mock.Received().StringGet(IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs index b8cf9a4b9..45c9621a4 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs @@ -7,6 +7,7 @@ using NSubstitute; using StackExchange.Redis.KeyspaceIsolation; using Xunit; +using static StackExchange.Redis.Tests.KeyPrefixedDatabaseTests; // for IsKeys etc namespace StackExchange.Redis.Tests { @@ -177,9 +178,8 @@ public async Task HyperLogLogMergeAsync_1() public async Task HyperLogLogMergeAsync_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.HyperLogLogMergeAsync("destination", keys, CommandFlags.None); - await mock.Received().HyperLogLogMergeAsync("prefix:destination", Arg.Is(valid), CommandFlags.None); + await mock.Received().HyperLogLogMergeAsync("prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] @@ -214,9 +214,8 @@ public async Task KeyDeleteAsync_1() public async Task KeyDeleteAsync_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.KeyDeleteAsync(keys, CommandFlags.None); - await mock.Received().KeyDeleteAsync(Arg.Is(valid), CommandFlags.None); + await mock.Received().KeyDeleteAsync(IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] @@ -538,9 +537,8 @@ public async Task ScriptEvaluateAsync_1() byte[] hash = Array.Empty(); RedisValue[] values = Array.Empty(); RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.ScriptEvaluateAsync(hash, keys, values, CommandFlags.None); - await mock.Received().ScriptEvaluateAsync(hash, Arg.Is(valid), values, CommandFlags.None); + await mock.Received().ScriptEvaluateAsync(hash, IsKeys("prefix:a", "prefix:b"), values, CommandFlags.None); } [Fact] @@ -548,9 +546,8 @@ public async Task ScriptEvaluateAsync_2() { RedisValue[] values = Array.Empty(); RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.ScriptEvaluateAsync("script", keys, values, CommandFlags.None); - await mock.Received().ScriptEvaluateAsync(script: "script", keys: Arg.Is(valid), values: values, flags: CommandFlags.None); + await mock.Received().ScriptEvaluateAsync(script: "script", keys: IsKeys("prefix:a", "prefix:b"), values: values, flags: CommandFlags.None); } [Fact] @@ -579,9 +576,8 @@ public async Task SetCombineAndStoreAsync_1() public async Task SetCombineAndStoreAsync_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.None); - await mock.Received().SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", Arg.Is(valid), CommandFlags.None); + await mock.Received().SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] @@ -595,9 +591,8 @@ public async Task SetCombineAsync_1() public async Task SetCombineAsync_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.SetCombineAsync(SetOperation.Intersect, keys, CommandFlags.None); - await mock.Received().SetCombineAsync(SetOperation.Intersect, Arg.Is(valid), CommandFlags.None); + await mock.Received().SetCombineAsync(SetOperation.Intersect, IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] @@ -618,9 +613,8 @@ public async Task SetContainsAsync_2() [Fact] public async Task SetIntersectionLengthAsync() { - var keys = new RedisKey[] { "key1", "key2" }; - await prefixed.SetIntersectionLengthAsync(keys); - await mock.Received().SetIntersectionLengthAsync(keys, 0, CommandFlags.None); + await prefixed.SetIntersectionLengthAsync(["key1", "key2"]); + await mock.Received().SetIntersectionLengthAsync(IsKeys("prefix:key1", "prefix:key2"), 0, CommandFlags.None); } [Fact] @@ -694,26 +688,24 @@ public async Task SetRemoveAsync_2() public async Task SortAndStoreAsync() { RedisValue[] get = ["a", "#"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; await prefixed.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.None); await prefixed.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.None); - await mock.Received().SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", Arg.Is(valid), CommandFlags.None); - await mock.Received().SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", Arg.Is(valid), CommandFlags.None); + await mock.Received().SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", IsValues("prefix:a", "#"), CommandFlags.None); + await mock.Received().SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", IsValues("prefix:a", "#"), CommandFlags.None); } [Fact] public async Task SortAsync() { RedisValue[] get = ["a", "#"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; await prefixed.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.None); await prefixed.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.None); - await mock.Received().SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", Arg.Is(valid), CommandFlags.None); - await mock.Received().SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", Arg.Is(valid), CommandFlags.None); + await mock.Received().SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", IsValues("prefix:a", "#"), CommandFlags.None); + await mock.Received().SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", IsValues("prefix:a", "#"), CommandFlags.None); } [Fact] @@ -742,17 +734,15 @@ public async Task SortedSetAddAsync_3() [Fact] public async Task SortedSetCombineAsync() { - RedisKey[] keys = ["a", "b"]; - await prefixed.SortedSetCombineAsync(SetOperation.Intersect, keys); - await mock.Received().SortedSetCombineAsync(SetOperation.Intersect, keys, null, Aggregate.Sum, CommandFlags.None); + await prefixed.SortedSetCombineAsync(SetOperation.Intersect, ["a", "b"]); + await mock.Received().SortedSetCombineAsync(SetOperation.Intersect, IsKeys("prefix:a", "prefix:b"), null, Aggregate.Sum, CommandFlags.None); } [Fact] public async Task SortedSetCombineWithScoresAsync() { - RedisKey[] keys = ["a", "b"]; - await prefixed.SortedSetCombineWithScoresAsync(SetOperation.Intersect, keys); - await mock.Received().SortedSetCombineWithScoresAsync(SetOperation.Intersect, keys, null, Aggregate.Sum, CommandFlags.None); + await prefixed.SortedSetCombineWithScoresAsync(SetOperation.Intersect, ["a", "b"]); + await mock.Received().SortedSetCombineWithScoresAsync(SetOperation.Intersect, IsKeys("prefix:a", "prefix:b"), null, Aggregate.Sum, CommandFlags.None); } [Fact] @@ -766,9 +756,8 @@ public async Task SortedSetCombineAndStoreAsync_1() public async Task SortedSetCombineAndStoreAsync_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.None); - await mock.Received().SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", Arg.Is(valid), CommandFlags.None); + await mock.Received().SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] @@ -788,9 +777,8 @@ public async Task SortedSetIncrementAsync() [Fact] public async Task SortedSetIntersectionLengthAsync() { - RedisKey[] keys = ["a", "b"]; - await prefixed.SortedSetIntersectionLengthAsync(keys, 1, CommandFlags.None); - await mock.Received().SortedSetIntersectionLengthAsync(keys, 1, CommandFlags.None); + await prefixed.SortedSetIntersectionLengthAsync(["a", "b"], 1, CommandFlags.None); + await mock.Received().SortedSetIntersectionLengthAsync(IsKeys("prefix:a", "prefix:b"), 1, CommandFlags.None); } [Fact] @@ -1171,45 +1159,40 @@ public async Task StringBitOperationAsync_1() public async Task StringBitOperationAsync_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.StringBitOperationAsync(Bitwise.Xor, "destination", keys, CommandFlags.None); - await mock.Received().StringBitOperationAsync(Bitwise.Xor, "prefix:destination", Arg.Is(valid), CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.Xor, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] public async Task StringBitOperationAsync_Diff() { RedisKey[] keys = ["x", "y1", "y2"]; - Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; await prefixed.StringBitOperationAsync(Bitwise.Diff, "destination", keys, CommandFlags.None); - await mock.Received().StringBitOperationAsync(Bitwise.Diff, "prefix:destination", Arg.Is(valid), CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.Diff, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); } [Fact] public async Task StringBitOperationAsync_Diff1() { RedisKey[] keys = ["x", "y1", "y2"]; - Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; await prefixed.StringBitOperationAsync(Bitwise.Diff1, "destination", keys, CommandFlags.None); - await mock.Received().StringBitOperationAsync(Bitwise.Diff1, "prefix:destination", Arg.Is(valid), CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.Diff1, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); } [Fact] public async Task StringBitOperationAsync_AndOr() { RedisKey[] keys = ["x", "y1", "y2"]; - Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; await prefixed.StringBitOperationAsync(Bitwise.AndOr, "destination", keys, CommandFlags.None); - await mock.Received().StringBitOperationAsync(Bitwise.AndOr, "prefix:destination", Arg.Is(valid), CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.AndOr, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); } [Fact] public async Task StringBitOperationAsync_One() { RedisKey[] keys = ["a", "b", "c"]; - Expression> valid = _ => _.Length == 3 && _[0] == "prefix:a" && _[1] == "prefix:b" && _[2] == "prefix:c"; await prefixed.StringBitOperationAsync(Bitwise.One, "destination", keys, CommandFlags.None); - await mock.Received().StringBitOperationAsync(Bitwise.One, "prefix:destination", Arg.Is(valid), CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.One, "prefix:destination", IsKeys("prefix:a", "prefix:b", "prefix:c"), CommandFlags.None); } [Fact] @@ -1251,9 +1234,8 @@ public async Task StringGetAsync_1() public async Task StringGetAsync_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.StringGetAsync(keys, CommandFlags.None); - await mock.Received().StringGetAsync(Arg.Is(valid), CommandFlags.None); + await mock.Received().StringGetAsync(IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } [Fact] @@ -1370,9 +1352,8 @@ public async Task KeyTouchAsync_1() public async Task KeyTouchAsync_2() { RedisKey[] keys = ["a", "b"]; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.KeyTouchAsync(keys, CommandFlags.None); - await mock.Received().KeyTouchAsync(Arg.Is(valid), CommandFlags.None); + await mock.Received().KeyTouchAsync(IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } } } From 6d088973d87b195b07a05c452c0f88b9c1ae0339 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 23 Jul 2025 11:55:52 +0100 Subject: [PATCH 15/19] add missing key-prefix tests --- .../KeyPrefixedDatabaseTests.cs | 360 ++++++++++++++++ .../KeyPrefixedTests.cs | 385 ++++++++++++++++++ 2 files changed, 745 insertions(+) diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs index d830f4f8b..571961eb0 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs @@ -1431,4 +1431,364 @@ public void StringSetRange() prefixed.StringSetRange("key", 123, "value", CommandFlags.None); mock.Received().StringSetRange("prefix:key", 123, "value", CommandFlags.None); } + + [Fact] + public void Execute_1() + { + prefixed.Execute("CUSTOM", "arg1", (RedisKey)"arg2"); + mock.Received().Execute("CUSTOM", Arg.Is(args => args.Length == 2 && args[0].Equals("arg1") && args[1].Equals((RedisKey)"prefix:arg2")), CommandFlags.None); + } + + [Fact] + public void Execute_2() + { + var args = new List { "arg1", (RedisKey)"arg2" }; + prefixed.Execute("CUSTOM", args, CommandFlags.None); + mock.Received().Execute("CUSTOM", Arg.Is>(a => a.Count == 2 && a.ElementAt(0).Equals("arg1") && a.ElementAt(1).Equals((RedisKey)"prefix:arg2"))!, CommandFlags.None); + } + + [Fact] + public void GeoAdd_1() + { + prefixed.GeoAdd("key", 1.23, 4.56, "member", CommandFlags.None); + mock.Received().GeoAdd("prefix:key", 1.23, 4.56, "member", CommandFlags.None); + } + + [Fact] + public void GeoAdd_2() + { + var geoEntry = new GeoEntry(1.23, 4.56, "member"); + prefixed.GeoAdd("key", geoEntry, CommandFlags.None); + mock.Received().GeoAdd("prefix:key", geoEntry, CommandFlags.None); + } + + [Fact] + public void GeoAdd_3() + { + var geoEntries = new GeoEntry[] { new GeoEntry(1.23, 4.56, "member1") }; + prefixed.GeoAdd("key", geoEntries, CommandFlags.None); + mock.Received().GeoAdd("prefix:key", geoEntries, CommandFlags.None); + } + + [Fact] + public void GeoRemove() + { + prefixed.GeoRemove("key", "member", CommandFlags.None); + mock.Received().GeoRemove("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public void GeoDistance() + { + prefixed.GeoDistance("key", "member1", "member2", GeoUnit.Meters, CommandFlags.None); + mock.Received().GeoDistance("prefix:key", "member1", "member2", GeoUnit.Meters, CommandFlags.None); + } + + [Fact] + public void GeoHash_1() + { + prefixed.GeoHash("key", "member", CommandFlags.None); + mock.Received().GeoHash("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public void GeoHash_2() + { + var members = new RedisValue[] { "member1", "member2" }; + prefixed.GeoHash("key", members, CommandFlags.None); + mock.Received().GeoHash("prefix:key", members, CommandFlags.None); + } + + [Fact] + public void GeoPosition_1() + { + prefixed.GeoPosition("key", "member", CommandFlags.None); + mock.Received().GeoPosition("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public void GeoPosition_2() + { + var members = new RedisValue[] { "member1", "member2" }; + prefixed.GeoPosition("key", members, CommandFlags.None); + mock.Received().GeoPosition("prefix:key", members, CommandFlags.None); + } + + [Fact] + public void GeoRadius_1() + { + prefixed.GeoRadius("key", "member", 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + mock.Received().GeoRadius("prefix:key", "member", 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public void GeoRadius_2() + { + prefixed.GeoRadius("key", 1.23, 4.56, 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + mock.Received().GeoRadius("prefix:key", 1.23, 4.56, 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public void GeoSearch_1() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + prefixed.GeoSearch("key", "member", shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + mock.Received().GeoSearch("prefix:key", "member", shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public void GeoSearch_2() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + prefixed.GeoSearch("key", 1.23, 4.56, shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + mock.Received().GeoSearch("prefix:key", 1.23, 4.56, shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public void GeoSearchAndStore_1() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + prefixed.GeoSearchAndStore("source", "destination", "member", shape, 10, true, Order.Ascending, false, CommandFlags.None); + mock.Received().GeoSearchAndStore("prefix:source", "prefix:destination", "member", shape, 10, true, Order.Ascending, false, CommandFlags.None); + } + + [Fact] + public void GeoSearchAndStore_2() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + prefixed.GeoSearchAndStore("source", "destination", 1.23, 4.56, shape, 10, true, Order.Ascending, false, CommandFlags.None); + mock.Received().GeoSearchAndStore("prefix:source", "prefix:destination", 1.23, 4.56, shape, 10, true, Order.Ascending, false, CommandFlags.None); + } + + [Fact] + public void HashFieldExpire_1() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + var expiry = TimeSpan.FromSeconds(60); + prefixed.HashFieldExpire("key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + mock.Received().HashFieldExpire("prefix:key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + } + + [Fact] + public void HashFieldExpire_2() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + var expiry = DateTime.Now.AddMinutes(1); + prefixed.HashFieldExpire("key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + mock.Received().HashFieldExpire("prefix:key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + } + + [Fact] + public void HashFieldGetExpireDateTime() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + prefixed.HashFieldGetExpireDateTime("key", hashFields, CommandFlags.None); + mock.Received().HashFieldGetExpireDateTime("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashFieldPersist() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + prefixed.HashFieldPersist("key", hashFields, CommandFlags.None); + mock.Received().HashFieldPersist("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashFieldGetTimeToLive() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + prefixed.HashFieldGetTimeToLive("key", hashFields, CommandFlags.None); + mock.Received().HashFieldGetTimeToLive("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashGetLease() + { + prefixed.HashGetLease("key", "field", CommandFlags.None); + mock.Received().HashGetLease("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public void HashFieldGetAndDelete_1() + { + prefixed.HashFieldGetAndDelete("key", "field", CommandFlags.None); + mock.Received().HashFieldGetAndDelete("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public void HashFieldGetAndDelete_2() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + prefixed.HashFieldGetAndDelete("key", hashFields, CommandFlags.None); + mock.Received().HashFieldGetAndDelete("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashFieldGetLeaseAndDelete() + { + prefixed.HashFieldGetLeaseAndDelete("key", "field", CommandFlags.None); + mock.Received().HashFieldGetLeaseAndDelete("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public void HashFieldGetAndSetExpiry_1() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.HashFieldGetAndSetExpiry("key", "field", expiry, false, CommandFlags.None); + mock.Received().HashFieldGetAndSetExpiry("prefix:key", "field", expiry, false, CommandFlags.None); + } + + [Fact] + public void HashFieldGetAndSetExpiry_2() + { + var expiry = DateTime.Now.AddMinutes(5); + prefixed.HashFieldGetAndSetExpiry("key", "field", expiry, CommandFlags.None); + mock.Received().HashFieldGetAndSetExpiry("prefix:key", "field", expiry, CommandFlags.None); + } + + [Fact] + public void HashFieldGetLeaseAndSetExpiry_1() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.HashFieldGetLeaseAndSetExpiry("key", "field", expiry, false, CommandFlags.None); + mock.Received().HashFieldGetLeaseAndSetExpiry("prefix:key", "field", expiry, false, CommandFlags.None); + } + + [Fact] + public void HashFieldGetLeaseAndSetExpiry_2() + { + var expiry = DateTime.Now.AddMinutes(5); + prefixed.HashFieldGetLeaseAndSetExpiry("key", "field", expiry, CommandFlags.None); + mock.Received().HashFieldGetLeaseAndSetExpiry("prefix:key", "field", expiry, CommandFlags.None); + } + [Fact] + public void StringGetLease() + { + prefixed.StringGetLease("key", CommandFlags.None); + mock.Received().StringGetLease("prefix:key", CommandFlags.None); + } + + [Fact] + public void StringGetSetExpiry_1() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.StringGetSetExpiry("key", expiry, CommandFlags.None); + mock.Received().StringGetSetExpiry("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public void StringGetSetExpiry_2() + { + var expiry = DateTime.Now.AddMinutes(5); + prefixed.StringGetSetExpiry("key", expiry, CommandFlags.None); + mock.Received().StringGetSetExpiry("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public void StringSetAndGet_1() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.StringSetAndGet("key", "value", expiry, When.Always, CommandFlags.None); + mock.Received().StringSetAndGet("prefix:key", "value", expiry, When.Always, CommandFlags.None); + } + + [Fact] + public void StringSetAndGet_2() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.StringSetAndGet("key", "value", expiry, false, When.Always, CommandFlags.None); + mock.Received().StringSetAndGet("prefix:key", "value", expiry, false, When.Always, CommandFlags.None); + } + [Fact] + public void StringLongestCommonSubsequence() + { + prefixed.StringLongestCommonSubsequence("key1", "key2", CommandFlags.None); + mock.Received().StringLongestCommonSubsequence("prefix:key1", "prefix:key2", CommandFlags.None); + } + + [Fact] + public void StringLongestCommonSubsequenceLength() + { + prefixed.StringLongestCommonSubsequenceLength("key1", "key2", CommandFlags.None); + mock.Received().StringLongestCommonSubsequenceLength("prefix:key1", "prefix:key2", CommandFlags.None); + } + + [Fact] + public void StringLongestCommonSubsequenceWithMatches() + { + prefixed.StringLongestCommonSubsequenceWithMatches("key1", "key2", 5, CommandFlags.None); + mock.Received().StringLongestCommonSubsequenceWithMatches("prefix:key1", "prefix:key2", 5, CommandFlags.None); + } + [Fact] + public void IsConnected() + { + prefixed.IsConnected("key", CommandFlags.None); + mock.Received().IsConnected("prefix:key", CommandFlags.None); + } + [Fact] + public void StreamAdd_WithTrimMode_1() + { + prefixed.StreamAdd("key", "field", "value", "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + mock.Received().StreamAdd("prefix:key", "field", "value", "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public void StreamAdd_WithTrimMode_2() + { + var fields = new NameValueEntry[] { new NameValueEntry("field", "value") }; + prefixed.StreamAdd("key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + mock.Received().StreamAdd("prefix:key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public void StreamTrim_WithMode() + { + prefixed.StreamTrim("key", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + mock.Received().StreamTrim("prefix:key", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public void StreamTrimByMinId_WithMode() + { + prefixed.StreamTrimByMinId("key", "1111111111", false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + mock.Received().StreamTrimByMinId("prefix:key", "1111111111", false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public void StreamReadGroup_WithNoAck_1() + { + prefixed.StreamReadGroup("key", "group", "consumer", "0-0", 10, true, CommandFlags.None); + mock.Received().StreamReadGroup("prefix:key", "group", "consumer", "0-0", 10, true, CommandFlags.None); + } + + [Fact] + public void StreamReadGroup_WithNoAck_2() + { + var streamPositions = new StreamPosition[] { new StreamPosition("key", "0-0") }; + prefixed.StreamReadGroup(streamPositions, "group", "consumer", 10, true, CommandFlags.None); + mock.Received().StreamReadGroup(streamPositions, "group", "consumer", 10, true, CommandFlags.None); + } + + [Fact] + public void StreamTrim_Simple() + { + prefixed.StreamTrim("key", 1000, true, CommandFlags.None); + mock.Received().StreamTrim("prefix:key", 1000, true, CommandFlags.None); + } + + [Fact] + public void StreamReadGroup_Simple_1() + { + prefixed.StreamReadGroup("key", "group", "consumer", "0-0", 10, CommandFlags.None); + mock.Received().StreamReadGroup("prefix:key", "group", "consumer", "0-0", 10, CommandFlags.None); + } + + [Fact] + public void StreamReadGroup_Simple_2() + { + var streamPositions = new StreamPosition[] { new StreamPosition("key", "0-0") }; + prefixed.StreamReadGroup(streamPositions, "group", "consumer", 10, CommandFlags.None); + mock.Received().StreamReadGroup(streamPositions, "group", "consumer", 10, CommandFlags.None); + } } diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs index 45c9621a4..cbef3d9e7 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Net; using System.Text; @@ -1355,5 +1356,389 @@ public async Task KeyTouchAsync_2() await prefixed.KeyTouchAsync(keys, CommandFlags.None); await mock.Received().KeyTouchAsync(IsKeys("prefix:a", "prefix:b"), CommandFlags.None); } + [Fact] + public async Task ExecuteAsync_1() + { + await prefixed.ExecuteAsync("CUSTOM", "arg1", (RedisKey)"arg2"); + await mock.Received().ExecuteAsync("CUSTOM", Arg.Is(args => args.Length == 2 && args[0].Equals("arg1") && args[1].Equals((RedisKey)"prefix:arg2")), CommandFlags.None); + } + + [Fact] + public async Task ExecuteAsync_2() + { + var args = new List { "arg1", (RedisKey)"arg2" }; + await prefixed.ExecuteAsync("CUSTOM", args, CommandFlags.None); + await mock.Received().ExecuteAsync("CUSTOM", Arg.Is?>(a => a != null && a.Count == 2 && a.ElementAt(0).Equals("arg1") && a.ElementAt(1).Equals((RedisKey)"prefix:arg2")), CommandFlags.None); + } + [Fact] + public async Task GeoAddAsync_1() + { + await prefixed.GeoAddAsync("key", 1.23, 4.56, "member", CommandFlags.None); + await mock.Received().GeoAddAsync("prefix:key", 1.23, 4.56, "member", CommandFlags.None); + } + + [Fact] + public async Task GeoAddAsync_2() + { + var geoEntry = new GeoEntry(1.23, 4.56, "member"); + await prefixed.GeoAddAsync("key", geoEntry, CommandFlags.None); + await mock.Received().GeoAddAsync("prefix:key", geoEntry, CommandFlags.None); + } + + [Fact] + public async Task GeoAddAsync_3() + { + var geoEntries = new GeoEntry[] { new GeoEntry(1.23, 4.56, "member1") }; + await prefixed.GeoAddAsync("key", geoEntries, CommandFlags.None); + await mock.Received().GeoAddAsync("prefix:key", geoEntries, CommandFlags.None); + } + + [Fact] + public async Task GeoRemoveAsync() + { + await prefixed.GeoRemoveAsync("key", "member", CommandFlags.None); + await mock.Received().GeoRemoveAsync("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public async Task GeoDistanceAsync() + { + await prefixed.GeoDistanceAsync("key", "member1", "member2", GeoUnit.Meters, CommandFlags.None); + await mock.Received().GeoDistanceAsync("prefix:key", "member1", "member2", GeoUnit.Meters, CommandFlags.None); + } + + [Fact] + public async Task GeoHashAsync_1() + { + await prefixed.GeoHashAsync("key", "member", CommandFlags.None); + await mock.Received().GeoHashAsync("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public async Task GeoHashAsync_2() + { + var members = new RedisValue[] { "member1", "member2" }; + await prefixed.GeoHashAsync("key", members, CommandFlags.None); + await mock.Received().GeoHashAsync("prefix:key", members, CommandFlags.None); + } + + [Fact] + public async Task GeoPositionAsync_1() + { + await prefixed.GeoPositionAsync("key", "member", CommandFlags.None); + await mock.Received().GeoPositionAsync("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public async Task GeoPositionAsync_2() + { + var members = new RedisValue[] { "member1", "member2" }; + await prefixed.GeoPositionAsync("key", members, CommandFlags.None); + await mock.Received().GeoPositionAsync("prefix:key", members, CommandFlags.None); + } + + [Fact] + public async Task GeoRadiusAsync_1() + { + await prefixed.GeoRadiusAsync("key", "member", 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + await mock.Received().GeoRadiusAsync("prefix:key", "member", 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public async Task GeoRadiusAsync_2() + { + await prefixed.GeoRadiusAsync("key", 1.23, 4.56, 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + await mock.Received().GeoRadiusAsync("prefix:key", 1.23, 4.56, 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public async Task GeoSearchAsync_1() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + await prefixed.GeoSearchAsync("key", "member", shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + await mock.Received().GeoSearchAsync("prefix:key", "member", shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public async Task GeoSearchAsync_2() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + await prefixed.GeoSearchAsync("key", 1.23, 4.56, shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + await mock.Received().GeoSearchAsync("prefix:key", 1.23, 4.56, shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public async Task GeoSearchAndStoreAsync_1() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + await prefixed.GeoSearchAndStoreAsync("source", "destination", "member", shape, 10, true, Order.Ascending, false, CommandFlags.None); + await mock.Received().GeoSearchAndStoreAsync("prefix:source", "prefix:destination", "member", shape, 10, true, Order.Ascending, false, CommandFlags.None); + } + + [Fact] + public async Task GeoSearchAndStoreAsync_2() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + await prefixed.GeoSearchAndStoreAsync("source", "destination", 1.23, 4.56, shape, 10, true, Order.Ascending, false, CommandFlags.None); + await mock.Received().GeoSearchAndStoreAsync("prefix:source", "prefix:destination", 1.23, 4.56, shape, 10, true, Order.Ascending, false, CommandFlags.None); + } + [Fact] + public async Task HashFieldExpireAsync_1() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + var expiry = TimeSpan.FromSeconds(60); + await prefixed.HashFieldExpireAsync("key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + await mock.Received().HashFieldExpireAsync("prefix:key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + } + + [Fact] + public async Task HashFieldExpireAsync_2() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + var expiry = DateTime.Now.AddMinutes(1); + await prefixed.HashFieldExpireAsync("key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + await mock.Received().HashFieldExpireAsync("prefix:key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetExpireDateTimeAsync() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + await prefixed.HashFieldGetExpireDateTimeAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashFieldGetExpireDateTimeAsync("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public async Task HashFieldPersistAsync() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + await prefixed.HashFieldPersistAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashFieldPersistAsync("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetTimeToLiveAsync() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + await prefixed.HashFieldGetTimeToLiveAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashFieldGetTimeToLiveAsync("prefix:key", hashFields, CommandFlags.None); + } + [Fact] + public async Task HashGetLeaseAsync() + { + await prefixed.HashGetLeaseAsync("key", "field", CommandFlags.None); + await mock.Received().HashGetLeaseAsync("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetAndDeleteAsync_1() + { + await prefixed.HashFieldGetAndDeleteAsync("key", "field", CommandFlags.None); + await mock.Received().HashFieldGetAndDeleteAsync("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetAndDeleteAsync_2() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + await prefixed.HashFieldGetAndDeleteAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashFieldGetAndDeleteAsync("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetLeaseAndDeleteAsync() + { + await prefixed.HashFieldGetLeaseAndDeleteAsync("key", "field", CommandFlags.None); + await mock.Received().HashFieldGetLeaseAndDeleteAsync("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetAndSetExpiryAsync_1() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.HashFieldGetAndSetExpiryAsync("key", "field", expiry, false, CommandFlags.None); + await mock.Received().HashFieldGetAndSetExpiryAsync("prefix:key", "field", expiry, false, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetAndSetExpiryAsync_2() + { + var expiry = DateTime.Now.AddMinutes(5); + await prefixed.HashFieldGetAndSetExpiryAsync("key", "field", expiry, CommandFlags.None); + await mock.Received().HashFieldGetAndSetExpiryAsync("prefix:key", "field", expiry, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetLeaseAndSetExpiryAsync_1() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.HashFieldGetLeaseAndSetExpiryAsync("key", "field", expiry, false, CommandFlags.None); + await mock.Received().HashFieldGetLeaseAndSetExpiryAsync("prefix:key", "field", expiry, false, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetLeaseAndSetExpiryAsync_2() + { + var expiry = DateTime.Now.AddMinutes(5); + await prefixed.HashFieldGetLeaseAndSetExpiryAsync("key", "field", expiry, CommandFlags.None); + await mock.Received().HashFieldGetLeaseAndSetExpiryAsync("prefix:key", "field", expiry, CommandFlags.None); + } + [Fact] + public async Task StringGetLeaseAsync() + { + await prefixed.StringGetLeaseAsync("key", CommandFlags.None); + await mock.Received().StringGetLeaseAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task StringGetSetExpiryAsync_1() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.StringGetSetExpiryAsync("key", expiry, CommandFlags.None); + await mock.Received().StringGetSetExpiryAsync("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public async Task StringGetSetExpiryAsync_2() + { + var expiry = DateTime.Now.AddMinutes(5); + await prefixed.StringGetSetExpiryAsync("key", expiry, CommandFlags.None); + await mock.Received().StringGetSetExpiryAsync("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public async Task StringSetAndGetAsync_1() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.StringSetAndGetAsync("key", "value", expiry, When.Always, CommandFlags.None); + await mock.Received().StringSetAndGetAsync("prefix:key", "value", expiry, When.Always, CommandFlags.None); + } + + [Fact] + public async Task StringSetAndGetAsync_2() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.StringSetAndGetAsync("key", "value", expiry, false, When.Always, CommandFlags.None); + await mock.Received().StringSetAndGetAsync("prefix:key", "value", expiry, false, When.Always, CommandFlags.None); + } + [Fact] + public async Task StringLongestCommonSubsequenceAsync() + { + await prefixed.StringLongestCommonSubsequenceAsync("key1", "key2", CommandFlags.None); + await mock.Received().StringLongestCommonSubsequenceAsync("prefix:key1", "prefix:key2", CommandFlags.None); + } + + [Fact] + public async Task StringLongestCommonSubsequenceLengthAsync() + { + await prefixed.StringLongestCommonSubsequenceLengthAsync("key1", "key2", CommandFlags.None); + await mock.Received().StringLongestCommonSubsequenceLengthAsync("prefix:key1", "prefix:key2", CommandFlags.None); + } + + [Fact] + public async Task StringLongestCommonSubsequenceWithMatchesAsync() + { + await prefixed.StringLongestCommonSubsequenceWithMatchesAsync("key1", "key2", 5, CommandFlags.None); + await mock.Received().StringLongestCommonSubsequenceWithMatchesAsync("prefix:key1", "prefix:key2", 5, CommandFlags.None); + } + [Fact] + public async Task KeyIdleTimeAsync() + { + await prefixed.KeyIdleTimeAsync("key", CommandFlags.None); + await mock.Received().KeyIdleTimeAsync("prefix:key", CommandFlags.None); + } + [Fact] + public async Task StreamAddAsync_WithTrimMode_1() + { + await prefixed.StreamAddAsync("key", "field", "value", "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + await mock.Received().StreamAddAsync("prefix:key", "field", "value", "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public async Task StreamAddAsync_WithTrimMode_2() + { + var fields = new NameValueEntry[] { new NameValueEntry("field", "value") }; + await prefixed.StreamAddAsync("key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + await mock.Received().StreamAddAsync("prefix:key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public async Task StreamTrimAsync_WithMode() + { + await prefixed.StreamTrimAsync("key", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + await mock.Received().StreamTrimAsync("prefix:key", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public async Task StreamTrimByMinIdAsync_WithMode() + { + await prefixed.StreamTrimByMinIdAsync("key", "1111111111", false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + await mock.Received().StreamTrimByMinIdAsync("prefix:key", "1111111111", false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public async Task StreamReadGroupAsync_WithNoAck_1() + { + await prefixed.StreamReadGroupAsync("key", "group", "consumer", "0-0", 10, true, CommandFlags.None); + await mock.Received().StreamReadGroupAsync("prefix:key", "group", "consumer", "0-0", 10, true, CommandFlags.None); + } + + [Fact] + public async Task StreamReadGroupAsync_WithNoAck_2() + { + var streamPositions = new StreamPosition[] { new StreamPosition("key", "0-0") }; + await prefixed.StreamReadGroupAsync(streamPositions, "group", "consumer", 10, true, CommandFlags.None); + await mock.Received().StreamReadGroupAsync(streamPositions, "group", "consumer", 10, true, CommandFlags.None); + } + + [Fact] + public async Task StreamTrimAsync_Simple() + { + await prefixed.StreamTrimAsync("key", 1000, true, CommandFlags.None); + await mock.Received().StreamTrimAsync("prefix:key", 1000, true, CommandFlags.None); + } + + [Fact] + public async Task StreamReadGroupAsync_Simple_1() + { + await prefixed.StreamReadGroupAsync("key", "group", "consumer", "0-0", 10, CommandFlags.None); + await mock.Received().StreamReadGroupAsync("prefix:key", "group", "consumer", "0-0", 10, CommandFlags.None); + } + + [Fact] + public async Task StreamReadGroupAsync_Simple_2() + { + var streamPositions = new StreamPosition[] { new StreamPosition("key", "0-0") }; + await prefixed.StreamReadGroupAsync(streamPositions, "group", "consumer", 10, CommandFlags.None); + await mock.Received().StreamReadGroupAsync(streamPositions, "group", "consumer", 10, CommandFlags.None); + } + + [Fact] + public void HashScanAsync() + { + var result = prefixed.HashScanAsync("key", "pattern*", 10, 1, 2, CommandFlags.None); + _ = mock.Received().HashScanAsync("prefix:key", "pattern*", 10, 1, 2, CommandFlags.None); + } + + [Fact] + public void HashScanNoValuesAsync() + { + var result = prefixed.HashScanNoValuesAsync("key", "pattern*", 10, 1, 2, CommandFlags.None); + _ = mock.Received().HashScanNoValuesAsync("prefix:key", "pattern*", 10, 1, 2, CommandFlags.None); + } + + [Fact] + public void SetScanAsync() + { + var result = prefixed.SetScanAsync("key", "pattern*", 10, 1, 2, CommandFlags.None); + _ = mock.Received().SetScanAsync("prefix:key", "pattern*", 10, 1, 2, CommandFlags.None); + } + + [Fact] + public void SortedSetScanAsync() + { + var result = prefixed.SortedSetScanAsync("key", "pattern*", 10, 1, 2, CommandFlags.None); + _ = mock.Received().SortedSetScanAsync("prefix:key", "pattern*", 10, 1, 2, CommandFlags.None); + } } } From 0e23e822e5975382473af33a9fd63652ae603da0 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 23 Jul 2025 12:53:40 +0100 Subject: [PATCH 16/19] CheckCommandResult: disambiguate key to prevent CI race --- .../RespProtocolTests.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/StackExchange.Redis.Tests/RespProtocolTests.cs b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs index 08e31b699..855ec96d1 100644 --- a/tests/StackExchange.Redis.Tests/RespProtocolTests.cs +++ b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs @@ -324,17 +324,28 @@ public async Task CheckCommandResult(string command, RedisProtocol protocol, Res var db = muxer.GetDatabase(); if (args.Length > 0) { - await db.KeyDeleteAsync((string)args[0]); - switch (args[0]) + var origKey = (string)args[0]; + switch (origKey) { case "ikey": - await db.StringSetAsync("ikey", "40"); - break; case "skey": - await db.SetAddAsync("skey", ["a", "b", "c"]); - break; case "hkey": - await db.HashSetAsync("hkey", [new("a", 1), new("b", 2), new("c", 3)]); + case "nkey": + var newKey = Me() + "_" + origKey; // disambiguate + args[0] = newKey; + await db.KeyDeleteAsync(newKey); // remove + switch (origKey) // initialize + { + case "ikey": + await db.StringSetAsync(newKey, "40"); + break; + case "skey": + await db.SetAddAsync(newKey, ["a", "b", "c"]); + break; + case "hkey": + await db.HashSetAsync(newKey, [new("a", 1), new("b", 2), new("c", 3)]); + break; + } break; } } From aad00c0fcb07349e132d9b5f0dc4cc3a65e5e2fc Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 23 Jul 2025 13:06:41 +0100 Subject: [PATCH 17/19] fix brittle ScanCancellable test --- tests/StackExchange.Redis.Tests/CancellationTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/StackExchange.Redis.Tests/CancellationTests.cs b/tests/StackExchange.Redis.Tests/CancellationTests.cs index f9f955eb8..aef2e9c71 100644 --- a/tests/StackExchange.Redis.Tests/CancellationTests.cs +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -156,6 +156,8 @@ public async Task CancellationDuringOperation_Async_CancelsGracefully(CancelStra [Fact] public async Task ScanCancellable() { + Skip.UnlessLongRunning(); // because of CLIENT PAUSE impact to unrelated tests + using var conn = Create(); var db = conn.GetDatabase(); var server = conn.GetServer(conn.GetEndPoints()[0]); @@ -182,7 +184,7 @@ public async Task ScanCancellable() var taken = watch.ElapsedMilliseconds; // Expected if cancellation happens during operation Log($"Cancelled after {taken}ms"); - Assert.True(taken < ConnectionPauseMilliseconds / 2, "Should have cancelled much sooner"); + Assert.True(taken < (ConnectionPauseMilliseconds * 3) / 4, $"Should have cancelled sooner; took {taken}ms"); Assert.Equal(cts.Token, oce.CancellationToken); } } From 5231a015d1b44d133324040260dd2c5ab6fe1189 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 23 Jul 2025 13:17:11 +0100 Subject: [PATCH 18/19] skip WithCancellation_CancelledToken_ThrowsOperationCanceledException on netfx due to unpredictable impl --- tests/StackExchange.Redis.Tests/CancellationTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/StackExchange.Redis.Tests/CancellationTests.cs b/tests/StackExchange.Redis.Tests/CancellationTests.cs index aef2e9c71..a512743f9 100644 --- a/tests/StackExchange.Redis.Tests/CancellationTests.cs +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -12,6 +12,10 @@ public class CancellationTests(ITestOutputHelper output, SharedConnectionFixture [Fact] public async Task WithCancellation_CancelledToken_ThrowsOperationCanceledException() { +#if NETFRAMEWORK + Skip.UnlessLongRunning(); // unpredictable on netfx due to weak WaitAsync impl +#endif + await using var conn = Create(); var db = conn.GetDatabase(); From 39a842d251821589cc60a0b6fef869e74466bfba Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 23 Jul 2025 13:22:59 +0100 Subject: [PATCH 19/19] release notes and version bump --- docs/ReleaseNotes.md | 5 +++-- version.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 758e0a30f..d912f0842 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -6,9 +6,10 @@ Current package versions: | ------------ | ----------------- | ----- | | [![StackExchange.Redis](https://img.shields.io/nuget/v/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis](https://img.shields.io/nuget/vpre/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis MyGet](https://img.shields.io/myget/stackoverflow/vpre/StackExchange.Redis.svg)](https://www.myget.org/feed/stackoverflow/package/nuget/StackExchange.Redis) | -## Unreleased +## Unreleased (2.9.xxx) -- nothing yet +- Add `HGETDEL`, `HGETEX` and `HSETEX` support ([#2863 by atakavci](https://github.com/StackExchange/StackExchange.Redis/pull/2863)) +- Fix key-prefix omission in `SetIntersectionLength` and `SortedSet{Combine[WithScores]|IntersectionLength}` ([#2863 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2863)) ## 2.8.58 diff --git a/version.json b/version.json index be7077002..63f4a5346 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "2.8", + "version": "2.9", "versionHeightOffset": -1, "assemblyVersion": "2.0", "publicReleaseRefSpec": [