diff --git a/Directory.Build.props b/Directory.Build.props index f3b57416..7cf2dad9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ Ugo Lattanzi - 6.0.8 + 6.0.9 pre netstandard2.0;net461;net472;netcoreapp3.0;netcoreapp3.1 diff --git a/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.cs b/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.cs index 7838d33d..ef463989 100644 --- a/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.cs +++ b/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.cs @@ -227,6 +227,25 @@ public partial interface IRedisDatabase Task SetAddAsync(string key, T item, CommandFlags flag = CommandFlags.None) where T : class; + /// + /// Run SPOP command https://redis.io/commands/spop + /// + /// The type of the expected object. + /// The key of the set + /// Behaviour markers associated with a given command + Task SetPopAsync(string key, CommandFlags flag = CommandFlags.None) + where T : class; + + /// + /// Run SPOP command https://redis.io/commands/spop + /// + /// The type of the expected object. + /// The key of the set + /// The number of elements to return + /// Behaviour markers associated with a given command + Task> SetPopAsync(string key, long count, CommandFlags flag = CommandFlags.None) + where T : class; + /// /// Returns if member is a member of the set stored at key. /// diff --git a/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.cs b/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.cs index b6b09820..2cb0814d 100644 --- a/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.cs +++ b/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.cs @@ -256,6 +256,33 @@ public Task SetAddAsync(string key, T item, CommandFlags flag = Command return Database.SetAddAsync(key, serializedObject, flag); } + /// + public async Task SetPopAsync(string key, CommandFlags flag = CommandFlags.None) + where T : class + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("key cannot be empty.", nameof(key)); + + var item = await Database.SetPopAsync(key, flag); + + if (item == RedisValue.Null) + return default; + + return Serializer.Deserialize(item); + } + + /// + public async Task> SetPopAsync(string key, long count, CommandFlags flag = CommandFlags.None) + where T : class + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("key cannot be empty.", nameof(key)); + + var items = await Database.SetPopAsync(key, count, flag); + + return items.Select(item => item == RedisValue.Null ? default : Serializer.Deserialize(item)); + } + /// public Task SetContainsAsync(string key, T item, CommandFlags flag = CommandFlags.None) where T : class diff --git a/tests/StackExchange.Redis.Extensions.Core.Tests/CacheClientTestBase.cs b/tests/StackExchange.Redis.Extensions.Core.Tests/CacheClientTestBase.cs index 340f34e4..b5d03a8f 100644 --- a/tests/StackExchange.Redis.Extensions.Core.Tests/CacheClientTestBase.cs +++ b/tests/StackExchange.Redis.Extensions.Core.Tests/CacheClientTestBase.cs @@ -301,6 +301,79 @@ public async Task SetAdd_With_An_Existing_Key_Should_Return_Valid_Data() Assert.Equal(keys.Length, values.Length); } + [Fact] + public async Task SetPop_With_An_Existing_Key_Should_Return_Valid_Data() + { + var values = Range(0, 5) + .Select(_ => new TestClass(Guid.NewGuid().ToString(), Guid.NewGuid().ToString())) + .ToArray(); + + foreach (var value in values) + { + await db.SetAddAsync("MySet", serializer.Serialize(value.Value)); + } + + var result = await Sut.GetDbFromConfiguration().SetPopAsync("MySet"); + Assert.NotNull(result); + Assert.Contains(values, v => v.Value == result); + + var members = await db.SetMembersAsync("MySet"); + var itemsLeft = members.Select(m => serializer.Deserialize(m)).ToArray(); + Assert.True(itemsLeft.Length == 4); + Assert.DoesNotContain(itemsLeft, l => l == result); + } + + [Fact] + public async Task SetPop_With_A_Non_Existing_Key_Should_Return_Null() + { + var result = await Sut.GetDbFromConfiguration().SetPopAsync("MySet"); + Assert.Null(result); + } + + [Fact] + public async Task SetPop_With_An_Empty_Key_Should_Throw_Exception() + { + await Assert.ThrowsAsync(() => Sut.GetDbFromConfiguration().SetPopAsync(string.Empty)); + } + + [Fact] + public async Task SetPop_Count_With_An_Existing_Key_Should_Return_Valid_Data() + { + var values = Range(0, 5) + .Select(_ => new TestClass(Guid.NewGuid().ToString(), Guid.NewGuid().ToString())) + .ToArray(); + + foreach (var value in values) + { + await db.SetAddAsync("MySet", serializer.Serialize(value.Value)); + } + + var result = await Sut.GetDbFromConfiguration().SetPopAsync("MySet", 3); + Assert.NotNull(result); + Assert.Equal(3, result.Count()); + foreach (var r in result) + Assert.Contains(values, v => v.Value == r); + + var members = await db.SetMembersAsync("MySet"); + var itemsLeft = members.Select(m => serializer.Deserialize(m)).ToArray(); + Assert.True(itemsLeft.Length == 2); + foreach (var r in result) + Assert.DoesNotContain(itemsLeft, l => l == r); + } + + [Fact] + public async Task SetPop_Count_With_A_Non_Existing_Key_Should_Return_Null() + { + var result = await Sut.GetDbFromConfiguration().SetPopAsync("MySet", 0); + Assert.Null(result); + } + + [Fact] + public async Task SetPop_Count_With_An_Empty_Key_Should_Throw_Exception() + { + await Assert.ThrowsAsync(() => Sut.GetDbFromConfiguration().SetPopAsync(string.Empty, 0)); + } + [Fact] public async Task SetMembers_With_Valid_Data_Should_Return_Correct_Keys() {