From 8173e554bc78378c464b4a874a97aa1cdf1d5e2b Mon Sep 17 00:00:00 2001 From: ttingen Date: Sun, 17 Jun 2018 00:28:16 -0400 Subject: [PATCH 01/15] Initial implementation of Redis Streams. The Streams data type is available in releases 5.0 RC1 and above. - Implemented Sync & Async methods for all Stream related commands (minus the blocking options) as of 5.0 RC1: XACK, XADD, XCLAIM, XDEL, XGROUP, XINFO, XLEN, XPENDING, XRANGE, XREAD, XREADGROUP, XREVRANGE, XTRIM - Added tests for the synchronous versions of the Streams API but the testing is a work in progress. Need to refactor for reuse within the streams tests and write a thorough suite of tests. - Added a NameValueEntry struct which mimicks HashEntry. Using HashEntry for the name/value pairs of stream entries seemed wrong. Perhaps refactor the usage of HashEntry to the more generic NameValueEntry and deprecate HashEntry? --- .../DatabaseWrapperTests.cs | 197 +++++ .../StackExchange.Redis.Tests.csproj | 4 + StackExchange.Redis.Tests/Streams.cs | 832 ++++++++++++++++++ StackExchange.Redis.Tests/WrapperBaseTests.cs | 197 +++++ .../StackExchange/Redis/Enums/RedisCommand.cs | 14 + .../StackExchange/Redis/ExtensionMethods.cs | 12 + .../Redis/Interfaces/IDatabase.cs | 314 ++++++- .../Redis/Interfaces/IDatabaseAsync.cs | 312 +++++++ .../KeyspaceIsolation/DatabaseWrapper.cs | 135 +++ .../Redis/KeyspaceIsolation/WrapperBase.cs | 135 +++ .../StackExchange/Redis/NameValueEntry.cs | 85 ++ .../StackExchange/Redis/RedisDatabase.cs | 771 ++++++++++++++++ .../StackExchange/Redis/RedisFeatures.cs | 8 +- .../StackExchange/Redis/RedisStream.cs | 32 + .../StackExchange/Redis/RedisStreamEntry.cs | 32 + .../StackExchange/Redis/ResultProcessor.cs | 478 ++++++++++ .../StackExchange/Redis/StreamConstants.cs | 59 ++ .../StackExchange/Redis/StreamConsumer.cs | 19 + .../StackExchange/Redis/StreamConsumerInfo.cs | 32 + .../StackExchange/Redis/StreamGroupInfo.cs | 32 + .../StackExchange/Redis/StreamInfo.cs | 54 ++ .../StackExchange/Redis/StreamPendingInfo.cs | 41 + .../Redis/StreamPendingMessageInfo.cs | 43 + 23 files changed, 3836 insertions(+), 2 deletions(-) create mode 100644 StackExchange.Redis.Tests/Streams.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/NameValueEntry.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/RedisStream.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/StreamConstants.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/StreamInfo.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs create mode 100644 StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs diff --git a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs index 1e584073d..acec39747 100644 --- a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs +++ b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs @@ -782,6 +782,203 @@ public void SortedSetScore() mock.Verify(_ => _.SortedSetScore("prefix:key", "member", CommandFlags.HighPriority)); } + [Fact] + public void StreamAcknowledge_1() + { + wrapper.StreamAcknowledge("key", "group", "0-0", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAcknowledge("prefix:key", "group", "0-0", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAcknowledge_2() + { + var messageIds = new string[] { "0-0", "0-1", "0-2" }; + wrapper.StreamAcknowledge("key", "group", messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAcknowledge("prefix:key", "group", messageIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAdd_1() + { + var fields = new NameValueEntry[0]; + wrapper.StreamAdd("key", fields, 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAdd("prefix:key", fields, 1000, true, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAdd_2() + { + var fields = new NameValueEntry[0]; + wrapper.StreamAdd("key", "0-0", fields, 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAdd("prefix:key", "0-0", fields, 1000, true, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAdd_3() + { + wrapper.StreamAdd("key", "field1", "value1", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAdd("prefix:key", "field1", "value1", 1000, true, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAdd_4() + { + wrapper.StreamAdd("key", "0-0", "field1", "value1", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAdd("prefix:key", "0-0", "field1", "value1", 1000, true, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamClaimMessages() + { + var messageIds = new string[0]; + wrapper.StreamClaimMessages("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamClaimMessages("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamClaimMessagesReturningIds() + { + var messageIds = new string[0]; + wrapper.StreamClaimMessagesReturningIds("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamClaimMessagesReturningIds("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamConsumerInfoGet() + { + wrapper.StreamConsumerInfoGet("key", "group", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamConsumerInfoGet("prefix:key", "group", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamCreateConsumerGroup() + { + wrapper.StreamCreateConsumerGroup("key", "group", "0-0", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamCreateConsumerGroup("prefix:key", "group", "0-0", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamGroupInfoGet() + { + wrapper.StreamGroupInfoGet("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamGroupInfoGet("prefix:key", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamInfoGet() + { + wrapper.StreamInfoGet("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamInfoGet("prefix:key", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamLength() + { + wrapper.StreamLength("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamLength("prefix:key", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamMessagesDelete() + { + var messageIds = new string[0] { }; + wrapper.StreamMessagesDelete("key", messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamMessagesDelete("prefix:key", messageIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamPendingInfoGet() + { + wrapper.StreamPendingInfoGet("key", "group", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingInfoGet("prefix:key", "group", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamPendingMessageInfoGet_1() + { + wrapper.StreamPendingMessageInfoGet("key", "group", "-", "+", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessageInfoGet("prefix:key", "group", "-", "+", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamPendingMessageInfoGet_2() + { + wrapper.StreamPendingMessageInfoGet("key", "group", "-", "+", 10, "consumer", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessageInfoGet("prefix:key", "group", "-", "+", 10, "consumer", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRange_1() + { + wrapper.StreamRange("key", "-", "+", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRange("prefix:key", "-", "+", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRange_2() + { + wrapper.StreamRange("key", "-", "+", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRange("prefix:key", "-", "+", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRangeReverse_1() + { + wrapper.StreamRangeReverse("key", "+", "-", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRangeReverse("prefix:key", "+", "-", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRangeReverse_2() + { + wrapper.StreamRangeReverse("key", "+", "-", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRangeReverse("prefix:key", "+", "-", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRead_1() + { + var keysAndIds = new KeyValuePair[0] { }; + wrapper.StreamRead(keysAndIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRead(keysAndIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRead_2() + { + wrapper.StreamRead("key", "0-0", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRead("prefix:key", "0-0", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRead_3() + { + var keysAndIds = new KeyValuePair[0] { }; + wrapper.StreamRead(keysAndIds, 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRead(keysAndIds, 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRead_4() + { + wrapper.StreamRead("key", "0-0", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRead("prefix:key", "0-0", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamStreamReadGroup() + { + wrapper.StreamReadGroup("key", "group", "consumer", "0-0", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamReadGroup("prefix:key", "group", "consumer", "0-0", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamTrim() + { + wrapper.StreamTrim("key", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamTrim("prefix:key", 1000, true, CommandFlags.HighPriority)); + } + [Fact] public void StringAppend() { diff --git a/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj b/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj index b11e297df..2e8af78b5 100644 --- a/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj +++ b/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj @@ -28,4 +28,8 @@ + + + + diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs new file mode 100644 index 000000000..bb7c224de --- /dev/null +++ b/StackExchange.Redis.Tests/Streams.cs @@ -0,0 +1,832 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +namespace StackExchange.Redis.Tests +{ + public class Streams : TestBase + { + public Streams(ITestOutputHelper output) : base(output) { } + + [Fact] + public void StreamAddSinglePairWithAutoId() + { + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + var messageId = db.StreamAdd(GetUniqueKey("auto_id"), "field1", "value1"); + + Assert.True(messageId != null && messageId.Length > 0); + } + } + + [Fact] + public void StreamAddMultipleValuePairsWithManualId() + { + var id = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}-0"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var key = GetUniqueKey("manual_id"); + + var fields = new NameValueEntry[2] + { + new NameValueEntry("field1", "value1"), + new NameValueEntry("field2", "value2") + }; + + var db = conn.GetDatabase(); + var messageId = db.StreamAdd(key, fields); + + var entries = db.StreamRange(key, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue); + + Assert.True(entries.Length == 1); + Assert.Equal(messageId, entries[0].Id); + Assert.True(entries[0].Values.Length == 2); + Assert.True(entries[0].Values[0].Name == "field1" && + entries[0].Values[0].Value == "value1"); + Assert.True(entries[0].Values[1].Name == "field2" && + entries[0].Values[1].Value == "value2"); + } + } + + [Fact] + public void StreamAddWithManualId() + { + var id = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}-0"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + var messageId = db.StreamAdd(GetUniqueKey("manual_id"), id, "field1", "value1"); + + Assert.Equal(id, messageId); + } + } + + [Fact] + public void StreamCreateConsumerGroup() + { + var key = GetUniqueKey("group_create"); + var groupName = "test_group"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Create a stream + db.StreamAdd(key, "field1", "value1"); + + // Create a group + var result = db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + + Assert.True(result); + } + } + + [Fact] + public void StreamConsumerGroupReadOnlyNewMessagesWithEmptyResponse() + { + var key = GetUniqueKey("group_read"); + var groupName = "test_group"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Create a stream + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + + // Create a group. + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.NewMessages); + + // Read, expect no messages + var entries = db.StreamReadGroup(key, groupName, "test_consumer", "0-0"); + + Assert.True(entries.Length == 0); + } + } + + [Fact] + public void StreamConsumerGroupReadFromStreamBeginning() + { + var key = GetUniqueKey("group_read_beginning"); + var groupName = "test_group"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + + var entries = db.StreamReadGroup(key, groupName, "test_consumer", "0-0"); + + Assert.True(entries.Length == 2); + Assert.True(id1 == entries[0].Id); + Assert.True(id2 == entries[1].Id); + } + } + + [Fact] + public void StreamConsumerGroupReadFromStreamBeginningWithCount() + { + var key = GetUniqueKey("group_read_with_count"); + var groupName = "test_group"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + + // Start reading after id1. + var entries = db.StreamReadGroup(key, groupName, "test_consumer", id1, 2); + + Assert.True(entries.Length == 2); + Assert.True(id2 == entries[0].Id); + Assert.True(id3 == entries[1].Id); + } + } + + [Fact] + public void StreamConsumerGroupAcknowledgeMessage() + { + var key = GetUniqueKey("group_ack"); + var groupName = "test_group"; + var consumer = "test_consumer"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + // Read from the beginning of the stream. + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + + // Read all 4 messages, they will be assigned to the consumer + var entries = db.StreamReadGroup(key, groupName, consumer, "0-0"); + + // Send XACK for 3 of the messages + var oneAck = db.StreamAcknowledge(key, groupName, id1); + var twoAck = db.StreamAcknowledge(key, groupName, new string[] { id3, id4 }); + + // Read the group again, it should only return the unacknowledged message. + var notAcknowledged = db.StreamReadGroup(key, groupName, consumer, "0-0"); + + Assert.True(entries.Length == 4); + Assert.Equal(1, oneAck); + Assert.Equal(2, twoAck); + Assert.True(notAcknowledged.Length == 1); + Assert.Equal(id2, notAcknowledged[0].Id); + } + } + + [Fact] + public void StreamConsumerGroupClaimMessages() + { + var key = GetUniqueKey("group_claim"); + var groupName = "test_group"; + var consumer1 = "test_consumer_1"; + var consumer2 = "test_consumer_2"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + // Read from the beginning of the stream. + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + + // Read a single message into the first consumer. + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.StreamMinValue, 1); + + // Read the remaining messages into the second consumer. + var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + + // Claim the 3 messages consumed by consumer2 for consumer1. + + // Get the pending messages for consumer2 + var pendingMessages = db.StreamPendingMessageInfoGet(key, groupName, + StreamConstants.StreamMinValue, + StreamConstants.StreamMaxValue, + 10, + consumer2); + + // Claim the messages for consumer1 + var messages = db.StreamClaimMessages(key, + groupName, + consumer1, + 0, // Min message idle time + messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray().ToStringArray()); + + // Now see how many messages are pending for each consumer + var pendingSummary = db.StreamPendingInfoGet(key, groupName); + + Assert.NotNull(pendingSummary); + Assert.NotNull(pendingSummary.Consumers); + Assert.True(pendingSummary.Consumers.Length == 1); + Assert.Equal(4, pendingSummary.Consumers[0].PendingMessageCount); + } + } + + [Fact] + public void StreamConsumerGroupViewPendingInfoSummary() + { + var key = GetUniqueKey("group_pending_info"); + var groupName = "test_group"; + var consumer1 = "test_consumer_1"; + var consumer2 = "test_consumer_2"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + // Read from the beginning of the stream. + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + + // Read a single message into the first consumer. + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.StreamMinValue, 1); + + // Read the remaining messages into the second consumer. + var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + + var pendingInfo = db.StreamPendingInfoGet(key, groupName); + + Assert.NotNull(pendingInfo); + Assert.Equal(4, pendingInfo.PendingMessageCount); + Assert.Equal(id1, pendingInfo.LowestPendingMessageId); + Assert.Equal(id4, pendingInfo.HighestPendingMessageId); + Assert.True(pendingInfo.Consumers.Length == 2); + + var consumer1Count = pendingInfo.Consumers.First(c => c.Name == consumer1).PendingMessageCount; + var consumer2Count = pendingInfo.Consumers.First(c => c.Name == consumer2).PendingMessageCount; + + Assert.Equal(1, consumer1Count); + Assert.Equal(3, consumer2Count); + } + } + + [Fact] + public void StreamConsumerGroupViewPendingMessageInfo() + { + var key = GetUniqueKey("group_pending_messages"); + var groupName = "test_group"; + var consumer1 = "test_consumer_1"; + var consumer2 = "test_consumer_2"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + // Read from the beginning of the stream. + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + + // Read a single message into the first consumer. + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.UndeliveredMessages, 1); + + // Read the remaining messages into the second consumer. + var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + + // Get the pending info about the messages themselves. + var pendingMessageInfoList = db.StreamPendingMessageInfoGet(key, groupName, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue, 10); + + Assert.NotNull(pendingMessageInfoList); + Assert.Equal(4, pendingMessageInfoList.Length); + Assert.Equal(consumer1, pendingMessageInfoList[0].ConsumerName); + Assert.Equal(1, pendingMessageInfoList[0].DeliveryCount); + Assert.True((int)pendingMessageInfoList[0].IdleTimeInMilliseconds > 0); + Assert.Equal(id1, pendingMessageInfoList[0].MessageId); + } + } + + [Fact] + public void StreamConsumerGroupViewPendingMessageInfoForConsumer() + { + var key = GetUniqueKey("group_pending_for_consumer"); + var groupName = "test_group"; + var consumer1 = "test_consumer_1"; + var consumer2 = "test_consumer_2"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + // Read from the beginning of the stream. + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + + // Read a single message into the first consumer. + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.UndeliveredMessages, 1); + + // Read the remaining messages into the second consumer. + var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + + // Get the pending info about the messages themselves. + var pendingMessageInfoList = db.StreamPendingMessageInfoGet(key, + groupName, + StreamConstants.StreamMinValue, + StreamConstants.StreamMaxValue, + 10, + consumer2); + + Assert.NotNull(pendingMessageInfoList); + Assert.Equal(3, pendingMessageInfoList.Length); + } + } + + [Fact] + public void StreamDeleteMessage() + { + var key = GetUniqueKey("delete_msg"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + var deletedCount = db.StreamMessagesDelete(key, new string[1] { id3 }, CommandFlags.None); + var messages = db.StreamRange(key, "-", "+"); + + Assert.Equal(1, deletedCount); + Assert.Equal(3, messages.Length); + } + } + + [Fact] + public void StreamGroupInfoGet() + { + var key = GetUniqueKey("group_info"); + var group1 = "test_group_1"; + var group2 = "test_group_2"; + var consumer1 = "test_consumer_1"; + var consumer2 = "test_consumer_2"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + // Read from the beginning of the stream. + db.StreamCreateConsumerGroup(key, group1, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, group2, StreamConstants.StreamMinValue); + + // Read a single message into the first consumer. + var consumer1Messages = db.StreamReadGroup(key, group1, consumer1, StreamConstants.UndeliveredMessages, 1); + + // Read the remaining messages into the second consumer. + var consumer2Messages = db.StreamReadGroup(key, group2, consumer2, StreamConstants.UndeliveredMessages); + + var groupInfoList = db.StreamGroupInfoGet(key); + + Assert.NotNull(groupInfoList); + Assert.Equal(2, groupInfoList.Length); + + Assert.Equal(group1, groupInfoList[0].Name); + Assert.Equal(1, groupInfoList[0].PendingMessageCount); + + Assert.Equal(group2, groupInfoList[1].Name); + Assert.Equal(4, groupInfoList[1].PendingMessageCount); + } + } + + [Fact] + public void StreamGroupConsumerInfoGet() + { + var key = GetUniqueKey("group_consumer_info"); + var group = "test_group"; + var consumer1 = "test_consumer_1"; + var consumer2 = "test_consumer_2"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + // Read from the beginning of the stream. + db.StreamCreateConsumerGroup(key, group, StreamConstants.StreamMinValue); + db.StreamReadGroup(key, group, consumer1, StreamConstants.UndeliveredMessages, 1); + db.StreamReadGroup(key, group, consumer2, StreamConstants.UndeliveredMessages); + + var consumerInfoList = db.StreamConsumerInfoGet(key, group); + + Assert.NotNull(consumerInfoList); + Assert.Equal(2, consumerInfoList.Length); + + Assert.Equal(consumer1, consumerInfoList[0].Name); + Assert.Equal(consumer2, consumerInfoList[1].Name); + + Assert.Equal(1, consumerInfoList[0].PendingMessageCount); + Assert.Equal(3, consumerInfoList[1].PendingMessageCount); + } + } + + [Fact] + public void StreamInfoGet() + { + var key = GetUniqueKey("stream_info"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + var streamInfo = db.StreamInfoGet(key); + + Assert.NotNull(streamInfo); + Assert.Equal(4, streamInfo.Length); + Assert.True(streamInfo.RadixTreeKeys > 0); + Assert.True(streamInfo.RadixTreeNodes > 0); + Assert.Equal(id1, streamInfo.FirstEntry.Id); + Assert.Equal(id4, streamInfo.LastEntry.Id); + } + } + + [Fact] + public void StreamVerifyLength() + { + var key = GetUniqueKey("len"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "fiedl2", "value2"); + + var len = db.StreamLength(key); + + Assert.Equal(2, len); + } + } + + [Fact] + public void StreamReadRange() + { + var key = GetUniqueKey("range"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var entries = db.StreamRange(key, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue); + + Assert.Equal(2, entries.Length); + Assert.Equal(id1, entries[0].Id); + Assert.Equal(id2, entries[1].Id); + } + } + + [Fact] + public void StreamReadRangeWithCount() + { + var key = GetUniqueKey("range_count"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var entries = db.StreamRange(key, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue, 1); + + Assert.True(entries.Length == 1); + Assert.Equal(id1, entries[0].Id); + } + } + + [Fact] + public void StreamReadRangeReverse() + { + var key = GetUniqueKey("rangerev"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var entries = db.StreamRangeReverse(key, StreamConstants.StreamMaxValue, StreamConstants.StreamMinValue); + + Assert.True(entries.Length == 2); + Assert.Equal(id2, entries[0].Id); + Assert.Equal(id1, entries[1].Id); + } + } + + [Fact] + public void StreamReadRangeReverseWithCount() + { + var key = GetUniqueKey("rangerev_count"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var entries = db.StreamRangeReverse(key, StreamConstants.StreamMaxValue, StreamConstants.StreamMinValue, 1); + + Assert.True(entries.Length == 1); + Assert.Equal(id2, entries[0].Id); + } + } + + [Fact] + public void StreamRead() + { + var key = GetUniqueKey("read"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + + var entries = db.StreamRead(key, "0-0"); + + Assert.True(entries.Length == 3); + Assert.Equal(id1, entries[0].Id); + Assert.Equal(id2, entries[1].Id); + Assert.Equal(id3, entries[2].Id); + } + } + + [Fact] + public void StreamReadWithAfterIdAndCount_1() + { + var key = GetUniqueKey("read"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + + var entries = db.StreamRead(key, id1, 1); + + Assert.True(entries.Length == 1); + Assert.Equal(id2, entries[0].Id); + } + } + + [Fact] + public void StreamReadWithAfterIdAndCount_2() + { + var key = GetUniqueKey("read"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + var entries = db.StreamRead(key, id1, 2); + + Assert.True(entries.Length == 2); + Assert.Equal(id2, entries[0].Id); + Assert.Equal(id3, entries[1].Id); + } + } + + [Fact] + public void StreamReadPastEndOfStream() + { + var key = GetUniqueKey("read_empty"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var entries = db.StreamRead(key, id2); + + Assert.True(entries.Length == 0); + } + } + + [Fact] + public void StreamReadMultipleStreams() + { + var key1 = GetUniqueKey("read_multi_1"); + var key2 = GetUniqueKey("read_multi_2"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key1, "field1", "value1"); + var id2 = db.StreamAdd(key1, "fiedl2", "value2"); + var id3 = db.StreamAdd(key2, "field3", "value3"); + var id4 = db.StreamAdd(key2, "field4", "value4"); + + var streamList = new KeyValuePair[2] + { + new KeyValuePair(key1, "0-0"), + new KeyValuePair(key2, "0-0") + }; + + var streams = db.StreamRead(streamList); + + Assert.True(streams.Length == 2); + + Assert.Equal(key1, streams[0].Key); + Assert.True(streams[0].Entries.Length == 2); + + Assert.Equal(key2, streams[1].Key); + Assert.True(streams[1].Entries.Length == 2); + } + } + + [Fact] + public void StreamReadMultipleStreamsWithReadPastSecondStream() + { + var key1 = GetUniqueKey("read_multi_1"); + var key2 = GetUniqueKey("read_multi_2"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key1, "field1", "value1"); + var id2 = db.StreamAdd(key1, "fiedl2", "value2"); + var id3 = db.StreamAdd(key2, "field3", "value3"); + var id4 = db.StreamAdd(key2, "field4", "value4"); + + var streamList = new KeyValuePair[2] + { + new KeyValuePair(key1, "0-0"), + new KeyValuePair(key2, id4) // read past the end of stream # 2 + }; + + var streams = db.StreamRead(streamList); + + // We should only get the first stream back. + Assert.True(streams.Length == 1); + + Assert.Equal(key1, streams[0].Key); + Assert.True(streams[0].Entries.Length == 2); + } + } + + [Fact] + public void StreamReadMultipleStreamsWithEmptyResponse() + { + var key1 = GetUniqueKey("read_multi_1"); + var key2 = GetUniqueKey("read_multi_2"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + var id1 = db.StreamAdd(key1, "field1", "value1"); + var id2 = db.StreamAdd(key1, "fiedl2", "value2"); + var id3 = db.StreamAdd(key2, "field3", "value3"); + var id4 = db.StreamAdd(key2, "field4", "value4"); + + var streamList = new KeyValuePair[2] + { + new KeyValuePair(key1, id2), + new KeyValuePair(key2, id4) // read past the end of stream # 2 + }; + + var streams = db.StreamRead(streamList); + + Assert.True(streams.Length == 0); + } + } + + private string GetUniqueKey(string type) => $"{type}_stream_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}"; + } +} diff --git a/StackExchange.Redis.Tests/WrapperBaseTests.cs b/StackExchange.Redis.Tests/WrapperBaseTests.cs index ed6fb3722..ea52e28a8 100644 --- a/StackExchange.Redis.Tests/WrapperBaseTests.cs +++ b/StackExchange.Redis.Tests/WrapperBaseTests.cs @@ -740,6 +740,203 @@ public void SortedSetScoreAsync() mock.Verify(_ => _.SortedSetScoreAsync("prefix:key", "member", CommandFlags.HighPriority)); } + [Fact] + public void StreamAcknowledgeAsync_1() + { + wrapper.StreamAcknowledgeAsync("key", "group", "0-0", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAcknowledgeAsync("prefix:key", "group", "0-0", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAcknowledgeAsync_2() + { + var messageIds = new string[] { "0-0", "0-1", "0-2" }; + wrapper.StreamAcknowledgeAsync("key", "group", messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAcknowledgeAsync("prefix:key", "group", messageIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAddAsync_1() + { + var fields = new NameValueEntry[0]; + wrapper.StreamAddAsync("key", fields, 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAddAsync("prefix:key", fields, 1000, true, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAddAsync_2() + { + var fields = new NameValueEntry[0]; + wrapper.StreamAddAsync("key", "0-0", fields, 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAddAsync("prefix:key", "0-0", fields, 1000, true, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAddAsync_3() + { + wrapper.StreamAddAsync("key", "field1", "value1", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAddAsync("prefix:key", "field1", "value1", 1000, true, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamAddAsync_4() + { + wrapper.StreamAddAsync("key", "0-0", "field1", "value1", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAddAsync("prefix:key", "0-0", "field1", "value1", 1000, true, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamClaimMessagesAsync() + { + var messageIds = new string[0]; + wrapper.StreamClaimMessagesAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamClaimMessagesAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamClaimMessagesReturningIdsAsync() + { + var messageIds = new string[0]; + wrapper.StreamClaimMessagesReturningIdsAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamClaimMessagesReturningIdsAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamConsumerInfoGetAsync() + { + wrapper.StreamConsumerInfoGetAsync("key", "group", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamConsumerInfoGetAsync("prefix:key", "group", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamCreateConsumerGroupAsync() + { + wrapper.StreamCreateConsumerGroupAsync("key", "group", "0-0", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamCreateConsumerGroupAsync("prefix:key", "group", "0-0", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamGroupInfoGetAsync() + { + wrapper.StreamGroupInfoGetAsync("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamGroupInfoGetAsync("prefix:key", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamInfoGetAsync() + { + wrapper.StreamInfoGetAsync("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamInfoGetAsync("prefix:key", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamLengthAsync() + { + wrapper.StreamLengthAsync("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamLengthAsync("prefix:key", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamMessagesDeleteAsync() + { + var messageIds = new string[0] { }; + wrapper.StreamMessagesDeleteAsync("key", messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamMessagesDeleteAsync("prefix:key", messageIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamPendingInfoGetAsync() + { + wrapper.StreamPendingInfoGetAsync("key", "group", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingInfoGetAsync("prefix:key", "group", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamPendingMessageInfoGetAsync_1() + { + wrapper.StreamPendingMessageInfoGetAsync("key", "group", "-", "+", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessageInfoGetAsync("prefix:key", "group", "-", "+", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamPendingMessageInfoGetAsync_2() + { + wrapper.StreamPendingMessageInfoGetAsync("key", "group", "-", "+", 10, "consumer", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessageInfoGetAsync("prefix:key", "group", "-", "+", 10, "consumer", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRangeAsync_1() + { + wrapper.StreamRangeAsync("key", "-", "+", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRangeAsync("prefix:key", "-", "+", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRangeAsync_2() + { + wrapper.StreamRangeAsync("key", "-", "+", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRangeAsync("prefix:key", "-", "+", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRangeReverseAsync_1() + { + wrapper.StreamRangeReverseAsync("key", "+", "-", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRangeReverseAsync("prefix:key", "+", "-", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamRangeReverseAsync_2() + { + wrapper.StreamRangeReverseAsync("key", "+", "-", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRangeReverseAsync("prefix:key", "+", "-", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamReadAsync_1() + { + var keysAndIds = new KeyValuePair[0] { }; + wrapper.StreamReadAsync(keysAndIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamReadAsync(keysAndIds, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamReadAsync_2() + { + wrapper.StreamReadAsync("key", "0-0", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamReadAsync("prefix:key", "0-0", CommandFlags.HighPriority)); + } + + [Fact] + public void StreamReadAsync_3() + { + var keysAndIds = new KeyValuePair[0] { }; + wrapper.StreamReadAsync(keysAndIds, 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamReadAsync(keysAndIds, 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamReadAsync_4() + { + wrapper.StreamReadAsync("key", "0-0", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamReadAsync("prefix:key", "0-0", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamStreamReadGroupAsync() + { + wrapper.StreamReadGroupAsync("key", "group", "consumer", "0-0", 10, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamReadGroupAsync("prefix:key", "group", "consumer", "0-0", 10, CommandFlags.HighPriority)); + } + + [Fact] + public void StreamTrimAsync() + { + wrapper.StreamTrimAsync("key", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamTrimAsync("prefix:key", 1000, true, CommandFlags.HighPriority)); + } + [Fact] public void StringAppendAsync() { diff --git a/StackExchange.Redis/StackExchange/Redis/Enums/RedisCommand.cs b/StackExchange.Redis/StackExchange/Redis/Enums/RedisCommand.cs index 16fb9f736..5e6a66ada 100644 --- a/StackExchange.Redis/StackExchange/Redis/Enums/RedisCommand.cs +++ b/StackExchange.Redis/StackExchange/Redis/Enums/RedisCommand.cs @@ -163,6 +163,20 @@ internal enum RedisCommand WATCH, + XACK, + XADD, + XCLAIM, + XDEL, + XGROUP, + XINFO, + XLEN, + XPENDING, + XRANGE, + XREAD, + XREADGROUP, + XREVRANGE, + XTRIM, + ZADD, ZCARD, ZCOUNT, diff --git a/StackExchange.Redis/StackExchange/Redis/ExtensionMethods.cs b/StackExchange.Redis/StackExchange/Redis/ExtensionMethods.cs index 0eac4dfbd..ea3c1ec5b 100644 --- a/StackExchange.Redis/StackExchange/Redis/ExtensionMethods.cs +++ b/StackExchange.Redis/StackExchange/Redis/ExtensionMethods.cs @@ -122,6 +122,18 @@ public static Dictionary ToDictionary(this KeyValuePair + /// Create an array of RedisValues from an array of strings. + /// + /// The string array to convert to RedisValues + public static RedisValue[] ToRedisValueArray(this string[] values) + { + if (values == null) return null; + if (values.Length == 0) return nixValues; + return Array.ConvertAll(values, x => (RedisValue)x); + } + private static readonly string[] nix = new string[0]; /// /// Create an array of strings from an array of values diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs index f98178559..f28f4e314 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; @@ -1389,6 +1389,318 @@ IEnumerable SortedSetScan(RedisKey key, /// https://redis.io/commands/zscore double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + /// + /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group that received the message. + /// The ID of the message to acknowledge. + /// The flags to use for this operation. + /// The number of messages acknowledged. + /// https://redis.io/topics/streams-intro + RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None); + + /// + /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group that received the message. + /// The ID's of the messages to acknowledge. + /// The flags to use for this operation. + /// The number of messages acknowledged. + /// https://redis.io/topics/streams-intro + RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The fields and their associated values to set in the stream entry. + /// The flags to use for this operation. + /// The maximum length of the stream. + /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. + /// The ID of the newly created message. + /// https://redis.io/commands/xadd + string StreamAdd(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + + /// + /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The field name for the stream entry. + /// The value to set in the stream entry. + /// The flags to use for this operation. + /// The maximum length of the stream. + /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. + /// The ID of the newly created message. + /// https://redis.io/commands/xadd + string StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + + /// + /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The ID to assign to the stream entry. This must be a unique value per entry. + /// The field name for the stream entry. + /// The value to set in the stream entry. + /// The flags to use for this operation. + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The ID of the newly created message. + /// https://redis.io/commands/xadd + string StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + + /// + /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The ID to assign to the stream entry. This must be a unique value per entry. + /// The fields and their associated values to set in the stream entry. + /// The flags to use for this operation. + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The ID of the newly created message. + /// https://redis.io/commands/xadd + string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + + /// + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). + /// + /// The key of the stream. + /// The consumer group. + /// The consumer claiming the given message(s). + /// The minimum message idle time to allow the reassignment of the message(s). + /// The ID(s) of the message(s) to claim for the given consumer. + /// The flags to use for this operation. + /// The messages successfully claimed by the given consumer. + /// https://redis.io/topics/streams-intro + RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the ID's for the claimed message(s). + /// + /// The key of the stream. + /// The consumer group. + /// The consumer claiming the given message(s). + /// The minimum message idle time to allow the reassignment of the message(s). + /// The ID(s) of the message(s) to claim for the given consumer. + /// The flags to use for this operation. + /// The message ID(s) for the messages successfully claimed by the given consumer. + /// https://redis.io/topics/streams-intro + string[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Create a consumer group for the given stream. + /// + /// The key of the stream. + /// The name of the group to create. + /// The beginning position in the stream from which to read. Pass "$" or to read only new messages. + /// The flags to use for this operation. + /// True if the group was created. + /// https://redis.io/topics/streams-intro + bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the given stream. This is the equivalent of calling "XINFO STREAM key". + /// + /// The key of the stream. + /// The flags to use for this operation. + /// A instance with information about the stream. + /// https://redis.io/topics/streams-intro + StreamInfo StreamInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the groups created for the given stream. This is the equivalent of calling "XINFO GROUPS key". + /// + /// The key of the stream. + /// The flags to use for this operation. + /// An instance of for each of the stream's groups. + /// https://redis.io/topics/streams-intro + StreamGroupInfo[] StreamGroupInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the consumers for the given consumer group. This is the equivalent of calling "XINFO GROUPS key group". + /// + /// The key of the stream. + /// The consumer group name. + /// The flags to use for this operation. + /// An instance of for each of the consumer group's consumers. + /// https://redis.io/topics/streams-intro + StreamConsumerInfo[] StreamConsumerInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + /// Return the number of entries in a stream. + /// + /// The key of the stream. + /// The flags to use for this operation. + /// The number of entries inside the given stream. + /// https://redis.io/commands/xlen + long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Delete messages in the stream. + /// + /// The key of the stream. + /// The ID(s) of the message(s) to delete. + /// The flags to use for this operation. + /// Returns the number of messages successfully deleted from the stream. + /// https://redis.io/topics/streams-intro + long StreamMessagesDelete(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// View information about pending messages for a stream. A pending message is a message read using StreamReadGroup (XREADGROUP) but not yet acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group + /// The flags to use for this operation. + /// An instance of . contains the number of pending messages, the highest and lowest ID of the pending messages, and the consumers with their pending message count. + /// The equivalent of calling XPENDING key group. + /// https://redis.io/commands/xpending + StreamPendingInfo StreamPendingInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + /// View information about each pending message. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The maximum number of pending messages to return. + /// The flags to use for this operation. + /// An instance of for each pending message. + /// Equivalent of calling XPENDING key group start-id end-id count. + /// https://redis.io/commands/xpending + StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); + + /// + /// View information about each pending message. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The maximum number of pending messages to return. + /// The consumer name. + /// The flags to use for this operation. + /// An instance of for each pending message. + /// Equivalent of calling XPENDING key group start-id end-id count consumer-name. + /// https://redis.io/commands/xpending + StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); + + /// + /// Read a stream using the given range of ID's. + /// + /// The key of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xrange + RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None); + + /// + /// Read a stream in reverse order using the given range of ID's. + /// + /// The key of the stream. + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xrevrange + RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); + + /// + /// Read a stream in reverse order using the given range of ID's. + /// + /// The key of the stream. + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xrevrange + RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None); + + /// + /// Read a stream in reverse order using the given range of ID's. + /// + /// The key of the stream. + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xrevrange + RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None); + + /// + /// Read from a single stream. + /// + /// The key of the stream. + /// The ID from within the stream to begin reading. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// Equivalent of calling XREAD STREAMS key id. + /// https://redis.io/commands/xread + RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None); + + /// + /// Read from a single stream. + /// + /// The key of the stream. + /// The position from within the stream to begin reading. + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// Equivalent of calling XREAD COUNT num STREAMS key id. + /// https://redis.io/commands/xread + RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None); + + /// + /// Read from multiple streams. + /// + /// The list of streams and the ID from which to begin reading for each stream. + /// The flags to use for this operation. + /// An instance of for each stream. + /// Equivalent of calling XREAD STREAMS key1 key2 id1 id2. + /// https://redis.io/commands/xread + RedisStream[] StreamRead(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None); + + /// + /// Read from multiple streams. + /// + /// The list of streams and the ID from which to begin reading for each stream. + /// The maximum number of messages to return from each stream. + /// The flags to use for this operation. + /// An instance of for each stream. + /// Equivalent of calling XREAD COUNT num STREAMS key1 key2 id1 id2. + /// https://redis.io/commands/xread + RedisStream[] StreamRead(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None); + + /// + /// Read messages from a stream and an associated consumer group. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The consumer name. + /// The ID from within the stream to begin reading. + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xreadgroup + RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None); + + /// + /// Trim the stream to a specified maximum length. + /// + /// The key of the stream. + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The flags to use for this operation. + /// The number of messages removed from the stream. + /// https://redis.io/topics/streams-intro + RedisValue StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + /// /// If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, /// so APPEND will be similar to SET in this special case. diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs index ee9affec5..a360422ce 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs @@ -1299,6 +1299,318 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// https://redis.io/commands/zscore Task SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + /// + /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group that received the message. + /// The ID of the message to acknowledge. + /// The flags to use for this operation. + /// The number of messages acknowledged. + /// https://redis.io/topics/streams-intro + Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None); + + /// + /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group that received the message. + /// The ID's of the messages to acknowledge. + /// The flags to use for this operation. + /// The number of messages acknowledged. + /// https://redis.io/topics/streams-intro + Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The fields and their associated values to set in the stream entry. + /// The flags to use for this operation. + /// The maximum length of the stream. + /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. + /// The ID of the newly created message. + /// https://redis.io/commands/xadd + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + + /// + /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The field name for the stream entry. + /// The value to set in the stream entry. + /// The flags to use for this operation. + /// The maximum length of the stream. + /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. + /// The ID of the newly created message. + /// https://redis.io/commands/xadd + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + + /// + /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The ID to assign to the stream entry. This must be a unique value per entry. + /// The field name for the stream entry. + /// The value to set in the stream entry. + /// The flags to use for this operation. + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The ID of the newly created message. + /// https://redis.io/commands/xadd + Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + + /// + /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The ID to assign to the stream entry. This must be a unique value per entry. + /// The fields and their associated values to set in the stream entry. + /// The flags to use for this operation. + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The ID of the newly created message. + /// https://redis.io/commands/xadd + Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + + /// + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). + /// + /// The key of the stream. + /// The consumer group. + /// The consumer claiming the given message(s). + /// The minimum message idle time to allow the reassignment of the message(s). + /// The ID(s) of the message(s) to claim for the given consumer. + /// The flags to use for this operation. + /// The messages successfully claimed by the given consumer. + /// https://redis.io/topics/streams-intro + Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the ID's for the claimed message(s). + /// + /// The key of the stream. + /// The consumer group. + /// The consumer claiming the given message(s). + /// The minimum message idle time to allow the reassignment of the message(s). + /// The ID(s) of the message(s) to claim for the given consumer. + /// The flags to use for this operation. + /// The message ID(s) for the messages successfully claimed by the given consumer. + /// https://redis.io/topics/streams-intro + Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Create a consumer group for the given stream. + /// + /// The key of the stream. + /// The name of the group to create. + /// The beginning position in the stream from which to read. Pass "$" or to read only new messages. + /// The flags to use for this operation. + /// True if the group was created. + /// https://redis.io/topics/streams-intro + Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the given stream. This is the equivalent of calling "XINFO STREAM key". + /// + /// The key of the stream. + /// The flags to use for this operation. + /// A instance with information about the stream. + /// https://redis.io/topics/streams-intro + Task StreamInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the groups created for the given stream. This is the equivalent of calling "XINFO GROUPS key". + /// + /// The key of the stream. + /// The flags to use for this operation. + /// An instance of for each of the stream's groups. + /// https://redis.io/topics/streams-intro + Task StreamGroupInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the consumers for the given consumer group. This is the equivalent of calling "XINFO GROUPS key group". + /// + /// The key of the stream. + /// The consumer group name. + /// The flags to use for this operation. + /// An instance of for each of the consumer group's consumers. + /// https://redis.io/topics/streams-intro + Task StreamConsumerInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + /// Return the number of entries in a stream. + /// + /// The key of the stream. + /// The flags to use for this operation. + /// The number of entries inside the given stream. + /// https://redis.io/commands/xlen + Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Delete messages in the stream. + /// + /// The key of the stream. + /// The ID(s) of the message(s) to delete. + /// The flags to use for this operation. + /// Returns the number of messages successfully deleted from the stream. + /// https://redis.io/topics/streams-intro + Task StreamMessagesDeleteAsync(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// View information about pending messages for a stream. A pending message is a message read using StreamReadGroup (XREADGROUP) but not yet acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group + /// The flags to use for this operation. + /// An instance of . contains the number of pending messages, the highest and lowest ID of the pending messages, and the consumers with their pending message count. + /// The equivalent of calling XPENDING key group. + /// https://redis.io/commands/xpending + Task StreamPendingInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + /// View information about each pending message. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The maximum number of pending messages to return. + /// The flags to use for this operation. + /// An instance of for each pending message. + /// Equivalent of calling XPENDING key group start-id end-id count. + /// https://redis.io/commands/xpending + Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); + + /// + /// View information about each pending message. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The maximum number of pending messages to return. + /// The consumer name. + /// The flags to use for this operation. + /// An instance of for each pending message. + /// Equivalent of calling XPENDING key group start-id end-id count consumer-name. + /// https://redis.io/commands/xpending + Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); + + /// + /// Read a stream using the given range of ID's. + /// + /// The key of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xrange + Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None); + + /// + /// Read a stream using the given range of ID's. + /// + /// The key of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xrange + Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); + + /// + /// Read a stream in reverse order using the given range of ID's. + /// + /// The key of the stream. + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xrevrange + Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None); + + /// + /// Read a stream in reverse order using the given range of ID's. + /// + /// The key of the stream. + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xrevrange + Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None); + + /// + /// Read from a single stream. + /// + /// The key of the stream. + /// The ID from within the stream to begin reading. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// Equivalent of calling XREAD STREAMS key id. + /// https://redis.io/commands/xread + Task StreamReadAsync(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None); + + /// + /// Read from a single stream. + /// + /// The key of the stream. + /// The position from within the stream to begin reading. + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// Equivalent of calling XREAD COUNT num STREAMS key id. + /// https://redis.io/commands/xread + Task StreamReadAsync(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None); + + /// + /// Read from multiple streams. + /// + /// The list of streams and the ID from which to begin reading for each stream. + /// The flags to use for this operation. + /// An instance of for each stream. + /// Equivalent of calling XREAD STREAMS key1 key2 id1 id2. + /// https://redis.io/commands/xread + Task StreamReadAsync(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None); + + /// + /// Read from multiple streams. + /// + /// The list of streams and the ID from which to begin reading for each stream. + /// The maximum number of messages to return from each stream. + /// The flags to use for this operation. + /// An instance of for each stream. + /// Equivalent of calling XREAD COUNT num STREAMS key1 key2 id1 id2. + /// https://redis.io/commands/xread + Task StreamReadAsync(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None); + + /// + /// Read messages from a stream and an associated consumer group. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The consumer name. + /// The ID from within the stream to begin reading. + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// https://redis.io/commands/xreadgroup + Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None); + + /// + /// Trim the stream to a specified maximum length. + /// + /// The key of the stream. + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The flags to use for this operation. + /// The number of messages removed from the stream. + /// https://redis.io/topics/streams-intro + Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + /// /// If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, /// so APPEND will be similar to SET in this special case. diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs index f5ec1177f..b1eb12214 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -587,6 +587,141 @@ public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue return Inner.SortedSetScore(ToInner(key), member, flags); } + public RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAcknowledge(ToInner(key), groupName, messageId, flags); + } + + public RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAcknowledge(ToInner(key), groupName, messageIds, flags); + } + + public string StreamAdd(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAdd(ToInner(key), streamFields, maxLength, useApproximateMaxLength, flags); + } + + public string StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAdd(ToInner(key), streamField, streamValue, maxLength, useApproximateMaxLength, flags); + } + + public string StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAdd(ToInner(key), streamEntryId, streamField, streamValue, maxLength, useApproximateMaxLength, flags); + } + + public string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAdd(ToInner(key), streamEntryId, streamFields, maxLength, useApproximateMaxLength, flags); + } + + public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamClaimMessages(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + } + + public string[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamClaimMessagesReturningIds(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + } + + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamCreateConsumerGroup(ToInner(key), groupName, readFrom, flags); + } + + public StreamInfo StreamInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamInfoGet(ToInner(key), flags); + } + + public StreamGroupInfo[] StreamGroupInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamGroupInfoGet(ToInner(key), flags); + } + + public StreamConsumerInfo[] StreamConsumerInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamConsumerInfoGet(ToInner(key), groupName, flags); + } + + public long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamLength(ToInner(key), flags); + } + + public long StreamMessagesDelete(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamMessagesDelete(ToInner(key), messageIds, flags); + } + + public StreamPendingInfo StreamPendingInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamPendingInfoGet(ToInner(key), groupName, flags); + } + + public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamPendingMessageInfoGet(ToInner(key), groupName, minId, maxId, count, flags); + } + + public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamPendingMessageInfoGet(ToInner(key), groupName, minId, maxId, count, consumerName, flags); + } + + public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRange(ToInner(key), minId, maxId, flags); + } + + public RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRangeReverse(ToInner(key), maxId, minId, count, flags); + } + + public RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRangeReverse(ToInner(key), maxId, minId, flags); + } + + public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRange(ToInner(key), minId, maxId, count, flags); + } + + public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRead(ToInner(key), afterId, flags); + } + + public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRead(ToInner(key), afterId, count, flags); + } + + public RedisStream[] StreamRead(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRead(streamWithAfterIdList, flags); + } + + public RedisStream[] StreamRead(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRead(streamWithAfterIdList, countPerStream, flags); + } + + public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamReadGroup(ToInner(key), groupName, consumerName, readFromId, count, flags); + } + + public RedisValue StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamTrim(ToInner(key), maxLength, useApproximateMaxLength, flags); + } + public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { return Inner.StringAppend(ToInner(key), value, flags); diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs index fa6cb6182..f92555498 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs @@ -566,6 +566,141 @@ public Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, return Inner.SortedSetScoreAsync(ToInner(key), member, flags); } + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageId, flags); + } + + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageIds, flags); + } + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAddAsync(ToInner(key), streamFields, maxLength, useApproximateMaxLength, flags); + } + + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAddAsync(ToInner(key), streamField, streamValue, maxLength, useApproximateMaxLength, flags); + } + + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAddAsync(ToInner(key), streamEntryId, streamField, streamValue, maxLength, useApproximateMaxLength, flags); + } + + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamAddAsync(ToInner(key), streamEntryId, streamFields, maxLength, useApproximateMaxLength, flags); + } + + public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamClaimMessagesAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + } + + public Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamClaimMessagesReturningIdsAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + } + + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamCreateConsumerGroupAsync(ToInner(key), groupName, readFrom, flags); + } + + public Task StreamInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamInfoGetAsync(ToInner(key), flags); + } + + public Task StreamGroupInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamGroupInfoGetAsync(ToInner(key), flags); + } + + public Task StreamConsumerInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamConsumerInfoGetAsync(ToInner(key), groupName, flags); + } + + public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamLengthAsync(ToInner(key), flags); + } + + public Task StreamMessagesDeleteAsync(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamMessagesDeleteAsync(ToInner(key), messageIds, flags); + } + + public Task StreamPendingInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamPendingInfoGetAsync(ToInner(key), groupName, flags); + } + + public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamPendingMessageInfoGetAsync(ToInner(key), groupName, minId, maxId, count, flags); + } + + public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamPendingMessageInfoGetAsync(ToInner(key), groupName, minId, maxId, count, consumerName, flags); + } + + public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRangeAsync(ToInner(key), minId, maxId, flags); + } + + public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRangeAsync(ToInner(key), minId, maxId, count, flags); + } + + public Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRangeReverseAsync(ToInner(key), maxId, minId, count, flags); + } + + public Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamRangeReverseAsync(ToInner(key), maxId, minId, flags); + } + + public Task StreamReadAsync(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamReadAsync(ToInner(key), afterId, flags); + } + + public Task StreamReadAsync(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamReadAsync(ToInner(key), afterId, count, flags); + } + + public Task StreamReadAsync(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamReadAsync(streamWithAfterIdList, flags); + } + + public Task StreamReadAsync(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamReadAsync(streamWithAfterIdList, countPerStream, flags); + } + + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamReadGroupAsync(ToInner(key), groupName, consumerName, readFromId, count, flags); + } + + public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + return Inner.StreamTrimAsync(ToInner(key), maxLength, useApproximateMaxLength, flags); + } + public Task StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { return Inner.StringAppendAsync(ToInner(key), value, flags); diff --git a/StackExchange.Redis/StackExchange/Redis/NameValueEntry.cs b/StackExchange.Redis/StackExchange/Redis/NameValueEntry.cs new file mode 100644 index 000000000..17268d5b4 --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/NameValueEntry.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace StackExchange.Redis +{ + /// + /// Describes a value contained in a stream (a name/value pair). + /// + public struct NameValueEntry : IEquatable + { + internal readonly RedisValue name, value; + + /// + /// Initializes a value. + /// + /// The name for this hash entry. + /// The value for this hash entry. + public NameValueEntry(RedisValue name, RedisValue value) + { + this.name = name; + this.value = value; + } + + /// + /// The name of the hash field + /// + public RedisValue Name => name; + + /// + /// The value of the hash field + /// + public RedisValue Value => value; + + /// + /// Converts to a key/value pair + /// + /// The to create a from. + public static implicit operator KeyValuePair(NameValueEntry value) => + new KeyValuePair(value.name, value.value); + + /// + /// Converts from a key/value pair + /// + /// The to get a from. + public static implicit operator NameValueEntry(KeyValuePair value) => + new NameValueEntry(value.Key, value.Value); + + /// + /// See Object.ToString() + /// + public override string ToString() => name + ": " + value; + + /// + /// See Object.GetHashCode() + /// + public override int GetHashCode() => name.GetHashCode() ^ value.GetHashCode(); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public override bool Equals(object obj) => obj is NameValueEntry heObj && Equals(heObj); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public bool Equals(NameValueEntry other) => name == other.name && value == other.value; + + /// + /// Compares two values for equality + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(NameValueEntry x, NameValueEntry y) => x.name == y.name && x.value == y.value; + + /// + /// Compares two values for non-equality + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(NameValueEntry x, NameValueEntry y) => x.name != y.name || x.value != y.value; + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index a5016a445..1b8f2de6c 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -1579,6 +1579,570 @@ IEnumerable IDatabase.SortedSetScan(RedisKey key, RedisValue pat return ExecuteAsync(msg, ResultProcessor.NullableDouble); } + public RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageId); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageIds); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageId); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageIds); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public string StreamAdd(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage(key, + StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + flags, + streamFields); + + return ExecuteSync(msg, ResultProcessor.String); + } + + public string StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage(key, + StreamConstants.AutoGeneratedId, + maxLength, useApproximateMaxLength, + flags, + new NameValueEntry(streamField, streamValue)); + + return ExecuteSync(msg, ResultProcessor.String); + } + + public string StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage(key, + streamEntryId, + maxLength, + useApproximateMaxLength, + flags, + new NameValueEntry(streamField, streamValue)); + + return ExecuteSync(msg, ResultProcessor.String); + } + + public string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage(key, + streamEntryId, + maxLength, + useApproximateMaxLength, + flags, + streamFields); + + return ExecuteSync(msg, ResultProcessor.String); + } + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage(key, + StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + flags, + streamFields); + + return ExecuteAsync(msg, ResultProcessor.String); + } + + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + // @@ Think of better names for the input variables. + + var msg = GetStreamAddMessage(key, + StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + flags, + new NameValueEntry(streamField, streamValue)); + + return ExecuteAsync(msg, ResultProcessor.String); + } + + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage(key, + streamEntryId, + maxLength, + useApproximateMaxLength, + flags, + new NameValueEntry(streamField, streamValue)); + + return ExecuteAsync(msg, ResultProcessor.String); + } + + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage(key, + streamEntryId, + maxLength, + useApproximateMaxLength, + flags, + streamFields); + + return ExecuteAsync(msg, ResultProcessor.String); + } + + public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, flags, messageIds); + return ExecuteSync(msg, ResultProcessor.SingleStream); + } + + public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, flags, messageIds); + return ExecuteAsync(msg, ResultProcessor.SingleStream); + } + + public string[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, flags, messageIds, true); + return ExecuteSync(msg, ResultProcessor.StringArray); + } + + public Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, flags, messageIds, true); + return ExecuteAsync(msg, ResultProcessor.StringArray); + } + + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XGROUP, + new RedisValue[] + { + StreamConstants.Create, + key.AsRedisValue(), + groupName, + readFrom + }); + + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XGROUP, + key, + groupName, + readFrom); + + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public StreamConsumerInfo[] StreamConsumerInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, + new RedisValue[3] + { + StreamConstants.Consumers, + key.AsRedisValue(), + groupName + }); + return ExecuteSync(msg, ResultProcessor.StreamConsumerInfo); + } + + public Task StreamConsumerInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, + new RedisValue[3] + { + StreamConstants.Consumers, + key.AsRedisValue(), + groupName + }); + return ExecuteAsync(msg, ResultProcessor.StreamConsumerInfo); + } + + public StreamGroupInfo[] StreamGroupInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key); + return ExecuteSync(msg, ResultProcessor.StreamGroupInfo); + } + + public Task StreamGroupInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key); + return ExecuteAsync(msg, ResultProcessor.StreamGroupInfo); + } + + public StreamInfo StreamInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key); + return ExecuteSync(msg, ResultProcessor.StreamInfo); + } + + public Task StreamInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key); + return ExecuteAsync(msg, ResultProcessor.StreamInfo); + } + + public long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XLEN, key); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XLEN, key); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long StreamMessagesDelete(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XDEL, + key, + messageIds.ToRedisValueArray()); + + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StreamMessagesDeleteAsync(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XDEL, + key, + messageIds.ToRedisValueArray()); + + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public StreamPendingInfo StreamPendingInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName); + return ExecuteSync(msg, ResultProcessor.StreamPendingInfo); + } + + public Task StreamPendingInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName); + return ExecuteAsync(msg, ResultProcessor.StreamPendingInfo); + } + + public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, RedisValue.Null, flags); + return ExecuteSync(msg, ResultProcessor.StreamPendingMessages); + } + + public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, consumerName, flags); + return ExecuteSync(msg, ResultProcessor.StreamPendingMessages); + } + + public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, RedisValue.Null, flags); + return ExecuteAsync(msg, ResultProcessor.StreamPendingMessages); + } + + public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, consumerName, flags); + return ExecuteAsync(msg, ResultProcessor.StreamPendingMessages); + } + + public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None) + { + // Example: > XRANGE somestream - + + + var msg = Message.Create(Database, + flags, + RedisCommand.XRANGE, + key, + minId, + maxId); + + return ExecuteSync(msg, ResultProcessor.SingleStream); + } + + public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + { + if (count <= 0) + { + throw new ArgumentException("'count' must be greater than 0."); + } + + var msg = Message.Create(Database, + flags, + RedisCommand.XRANGE, + key, + new RedisValue[] + { + minId, + maxId, + StreamConstants.Count, + count + }); + + return ExecuteSync(msg, ResultProcessor.SingleStream); + } + + public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XRANGE, + key, + new RedisValue[] + { + minId, + maxId + }); + + return ExecuteAsync(msg, ResultProcessor.SingleStream); + } + + public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + { + if (count <= 0) + { + throw new ArgumentException("'count' must be greater than 0."); + } + + var msg = Message.Create(Database, + flags, + RedisCommand.XRANGE, + key, + new RedisValue[] + { + minId, + maxId, + StreamConstants.Count, + count + }); + + return ExecuteAsync(msg, ResultProcessor.SingleStream); + } + + public RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None) + { + // Example: > XRANGE somestream + - COUNT 1 + + if (count <= 0) + { + throw new ArgumentException("'count' must be greater than 0."); + } + + var msg = Message.Create(Database, + flags, + RedisCommand.XREVRANGE, + key, + new RedisValue[] + { + maxId, + minId, + StreamConstants.Count, + count + }); + + return ExecuteSync(msg, ResultProcessor.SingleStream); + } + + public RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None) + { + // Example: > XRANGE somestream + - + + var msg = Message.Create(Database, + flags, + RedisCommand.XREVRANGE, + key, + maxId, + minId); + + return ExecuteSync(msg, ResultProcessor.SingleStream); + } + + public Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None) + { + if (count <= 0) + { + throw new ArgumentException("'count' must be greater than 0."); + } + + var msg = Message.Create(Database, + flags, + RedisCommand.XREVRANGE, + key, + new RedisValue[] + { + maxId, + minId, + StreamConstants.Count, + count + }); + + return ExecuteAsync(msg, ResultProcessor.SingleStream); + } + + public Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XREVRANGE, + key, + maxId, + minId); + + return ExecuteAsync(msg, ResultProcessor.SingleStream); + } + + public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None) + { + // Example: > XREAD STREAMS writers 1526999352406-0 + var msg = Message.Create(Database, + flags, + RedisCommand.XREAD, + new RedisValue[] + { + StreamConstants.Streams, + key.AsRedisValue(), + afterId + }); + + return ExecuteSync(msg, ResultProcessor.SingleStreamWithNameSkip); + } + + public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) + { + // Example: > XREAD COUNT 2 STREAMS writers 1526999352406-0 + var msg = Message.Create(Database, + flags, + RedisCommand.XREAD, + new RedisValue[] + { + StreamConstants.Count, + count, + StreamConstants.Streams, + key.AsRedisValue(), + afterId + }); + + return ExecuteSync(msg, ResultProcessor.SingleStreamWithNameSkip); + } + + public RedisStream[] StreamRead(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None) + { + var msg = GetMultiStreamReadMessage(streamWithAfterIdList, flags); + return ExecuteSync(msg, ResultProcessor.MultiStream); + } + + public RedisStream[] StreamRead(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) + { + var msg = GetMultiStreamReadMessage(streamWithAfterIdList, countPerStream, flags); + return ExecuteSync(msg, ResultProcessor.MultiStream); + } + + public Task StreamReadAsync(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XREAD, + new RedisValue[] + { + StreamConstants.Streams, + key.AsRedisValue(), + afterId + }); + + return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip); + } + + public Task StreamReadAsync(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XREAD, + new RedisValue[] + { + StreamConstants.Count, + count, + StreamConstants.Streams, + key.AsRedisValue(), + afterId + }); + + return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip); + } + + public Task StreamReadAsync(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None) + { + var msg = GetMultiStreamReadMessage(streamWithAfterIdList, flags); + return ExecuteAsync(msg, ResultProcessor.MultiStream); + } + + public Task StreamReadAsync(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) + { + var msg = GetMultiStreamReadMessage(streamWithAfterIdList, countPerStream, flags); + return ExecuteAsync(msg, ResultProcessor.MultiStream); + } + + public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamReadGroupMessage(key, groupName, consumerName, readFromId, count, flags); + return ExecuteSync(msg, ResultProcessor.SingleStreamWithNameSkip); + } + + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamReadGroupMessage(key, groupName, consumerName, readFromId, count, flags); + return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip); + } + + public RedisValue StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XTRIM, + key, + StreamConstants.MaxLen, + useApproximateMaxLength ? StreamConstants.ApproximateMaxLen : StreamConstants.Blank, + maxLength); + + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, + flags, + RedisCommand.XTRIM, + key, + StreamConstants.MaxLen, + useApproximateMaxLength ? StreamConstants.ApproximateMaxLen : StreamConstants.Blank, + maxLength); + + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value); @@ -1936,6 +2500,48 @@ private RedisValue GetLexRange(RedisValue value, Exclude exclude, bool isStart) return result; } + private Message GetMultiStreamReadMessage(IList> streamWithAfterIdList, CommandFlags flags) + { + // See: https://redis.io/commands/xread. + + // + 1 because we need to make room for the STREAMS key word in the XREAD command. + var values = new RedisValue[(streamWithAfterIdList.Count * 2) + 1]; + var idOffset = streamWithAfterIdList.Count + 1; + + values[0] = StreamConstants.Streams; + + // The ID for a given stream is offset by the stream count plus the associated stream ID count. + + // Format: stream1 stream2 afterId1 afterId2 + for (var i = 0; i < streamWithAfterIdList.Count; i++) + { + values[i + 1] = streamWithAfterIdList[i].Key.AsRedisValue(); + values[idOffset++] = streamWithAfterIdList[i].Value; + } + + return Message.Create(Database, flags, RedisCommand.XREAD, values); + } + + private Message GetMultiStreamReadMessage(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags) + { + var values = new RedisValue[(streamWithAfterIdList.Count * 2) + 3]; + var idOffset = streamWithAfterIdList.Count + 3; + + values[0] = StreamConstants.Count; + values[1] = countPerStream; + values[2] = StreamConstants.Streams; + + // Duplicated from the overloaded method above. + // Is it worth refactoring the name and ID staggering to a separate method? + for (var i = 0; i < streamWithAfterIdList.Count; i++) + { + values[i + 3] = streamWithAfterIdList[i].Key.AsRedisValue(); + values[idOffset++] = streamWithAfterIdList[i].Value; + } + + return Message.Create(Database, flags, RedisCommand.XREAD, values); + } + private RedisValue GetRange(double value, Exclude exclude, bool isStart) { if (isStart) @@ -2164,6 +2770,171 @@ private Message GetSortedSetRemoveRangeByScoreMessage(RedisKey key, double start GetRange(start, exclude, true), GetRange(stop, exclude, false)); } + private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, CommandFlags flags, params string[] messageIds) + { + var arr = new RedisValue[messageIds.Length + 2]; + + var offset = 0; + + arr[offset++] = key.AsRedisValue(); + arr[offset++] = groupName; + + for (var i = 0; i < messageIds.Length; i++) + { + arr[offset++] = messageIds[i]; + } + + return Message.Create(Database, flags, RedisCommand.XACK, arr); + } + + private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags, params NameValueEntry[] streamFields) + { + // See https://redis.io/commands/xadd. + + if (streamFields == null) throw new ArgumentNullException(nameof(streamFields)); + if (streamFields.Length == 0) throw new ArgumentOutOfRangeException("streamFields must contain at least one field."); + + if (maxLength.HasValue && maxLength <= 0) + { + throw new ArgumentOutOfRangeException("maxLength must be greater than 0."); + } + + var includeMaxLen = maxLength.HasValue ? 2 : 0; + var includeApproxLen = maxLength.HasValue && useApproximateMaxLength ? 1 : 0; + + var totalLength = (streamFields.Length * 2) // Room for the name/value pairs + + 1 // The stream entry ID + + includeMaxLen // 2 or 0 (MAXLEN keyword & the count) + + includeApproxLen; // 1 or 0 + + var values = new RedisValue[totalLength]; + + var offset = 0; + + values[offset++] = entryId; + + if (maxLength.HasValue) + { + values[offset++] = StreamConstants.MaxLen; + + if (useApproximateMaxLength) + { + values[offset++] = StreamConstants.ApproximateMaxLen; + } + + values[offset++] = maxLength.Value; + } + + for (var i = 0; i < streamFields.Length; i++) + { + values[offset++] = streamFields[i].Name; + values[offset++] = streamFields[i].Value; + } + + return Message.Create(Database, flags, RedisCommand.XADD, key, values); + } + + private Message GetStreamClaimMessage(RedisKey key, RedisValue consumerGroup, RedisValue assignToConsumer, long minIdleTimeInMs, CommandFlags flags, string[] messageIds, bool returnJustIds = false) + { + if (messageIds == null || messageIds.Length == 0) + { + throw new ArgumentException("messageIds must contain at least 1 messageId."); + } + + // XCLAIM ... + var values = new RedisValue[4 + messageIds.Length + (returnJustIds ? 1 : 0)]; + + var offset = 0; + + values[offset++] = key.AsRedisValue(); + values[offset++] = consumerGroup; + values[offset++] = assignToConsumer; + values[offset++] = minIdleTimeInMs; + + for (var i = 0; i < messageIds.Length; i++) + { + values[offset++] = messageIds[i]; + } + + if (returnJustIds) + { + values[offset++] = StreamConstants.JustId; + } + + return Message.Create(Database, flags, RedisCommand.XCLAIM, values); + } + + private Message GetStreamPendingMessagesMessage(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags) + { + // > XPENDING mystream mygroup - + 10 [consumer name] + // 1) 1) 1526569498055 - 0 + // 2) "Bob" + // 3) (integer)74170458 + // 4) (integer)1 + // 2) 1) 1526569506935 - 0 + // 2) "Bob" + // 3) (integer)74170458 + // 4) (integer)1 + + // See https://redis.io/topics/streams-intro. + + if (count <= 0) + { + throw new ArgumentOutOfRangeException("count must be greater than 0."); + } + + var values = new RedisValue[consumerName == RedisValue.Null ? 5 : 6]; + + values[0] = key.AsRedisValue(); + values[1] = groupName; + values[2] = minId; + values[3] = maxId; + values[4] = count; + + if (consumerName != RedisValue.Null) + { + values[5] = consumerName; + } + + return Message.Create(Database, + flags, + RedisCommand.XPENDING, + values); + } + + private Message GetStreamReadGroupMessage(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count, CommandFlags flags) + { + // Example: > XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream > + if (count.HasValue && count <= 0) + { + throw new ArgumentOutOfRangeException("count must be greater than 0."); + } + + var totalValueCount = 6 + (count.HasValue ? 2 : 0); + var arr = new RedisValue[totalValueCount]; + + var offset = 0; + + arr[offset++] = StreamConstants.Group; + arr[offset++] = groupName; + arr[offset++] = consumerName; + + if (count.HasValue) + { + arr[offset++] = StreamConstants.Count; + arr[offset++] = count.Value; + } + + arr[offset++] = StreamConstants.Streams; + arr[offset++] = key.AsRedisValue(); + arr[offset] = readFromId; + + return Message.Create(Database, + flags, + RedisCommand.XREADGROUP, + arr); + } + private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags) { if (keys == null) throw new ArgumentNullException(nameof(keys)); diff --git a/StackExchange.Redis/StackExchange/Redis/RedisFeatures.cs b/StackExchange.Redis/StackExchange/Redis/RedisFeatures.cs index d0f32a6a3..9dd4469fc 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisFeatures.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisFeatures.cs @@ -28,7 +28,8 @@ public struct RedisFeatures v2_8_18 = new Version(2, 8, 18), v2_9_5 = new Version(2, 9, 5), v3_0_0 = new Version(3, 0, 0), - v3_2_0 = new Version(3, 2, 0); + v3_2_0 = new Version(3, 2, 0), + v4_9_1 = new Version(4, 9, 1); // 5.0 RC1 is version 4.9.1 private readonly Version version; /// @@ -120,6 +121,11 @@ public RedisFeatures(Version version) /// public bool SetVaradicAddRemove => Version >= v2_4_0; + /// + /// Are Redis Streams available? + /// + public bool Streams => Version >= v4_9_1; + /// /// Is STRLEN available? /// diff --git a/StackExchange.Redis/StackExchange/Redis/RedisStream.cs b/StackExchange.Redis/StackExchange/Redis/RedisStream.cs new file mode 100644 index 000000000..f0ce1ffbd --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/RedisStream.cs @@ -0,0 +1,32 @@ +namespace StackExchange.Redis +{ + /// + /// Describes a Redis Stream with an associated array of entries. + /// + public struct RedisStream + { + internal readonly RedisKey key; + internal readonly RedisStreamEntry[] entries; + + /// + /// Initializes a instance. + /// + /// The key for the stream. + /// An arry of entries contained within the stream. + public RedisStream(RedisKey key, RedisStreamEntry[] entries) + { + this.key = key; + this.entries = entries; + } + + /// + /// The key for the stream. + /// + public RedisKey Key => key; + + /// + /// An arry of entries contained within the stream. + /// + public RedisStreamEntry[] Entries => entries; + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs b/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs new file mode 100644 index 000000000..5be33a1c9 --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs @@ -0,0 +1,32 @@ +namespace StackExchange.Redis +{ + /// + /// Describes an entry contained in a Redis Stream. + /// + public struct RedisStreamEntry + { + internal readonly RedisValue id; + internal readonly NameValueEntry[] values; + + /// + /// Initializes a instance. + /// + /// The ID assigned to the message. + /// The values contained within the message. + public RedisStreamEntry(RedisValue id, NameValueEntry[] values) + { + this.id = id; + this.values = values; + } + + /// + /// The ID assigned to the message. + /// + public RedisValue Id => id; + + /// + /// The values contained within the message. + /// + public NameValueEntry[] Values => values; + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs b/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs index afc1cf3da..2eb7b0b84 100644 --- a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs +++ b/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs @@ -40,6 +40,9 @@ public static readonly ResultProcessor public static readonly ResultProcessor>[]> Info = new InfoProcessor(); + public static readonly MultiStreamProcessor + MultiStream = new MultiStreamProcessor(); + public static readonly ResultProcessor Int64 = new Int64Processor(), PubSubNumSub = new PubSubNumSubProcessor(); @@ -84,6 +87,27 @@ public static readonly ResultProcessor public static readonly SortedSetEntryArrayProcessor SortedSetWithScores = new SortedSetEntryArrayProcessor(); + public static readonly SingleStreamProcessor + SingleStream = new SingleStreamProcessor(); + + public static readonly SingleStreamProcessor + SingleStreamWithNameSkip = new SingleStreamProcessor(skipStreamName: true); + + public static readonly StreamConsumerInfoProcessor + StreamConsumerInfo = new StreamConsumerInfoProcessor(); + + public static readonly StreamGroupInfoProcessor + StreamGroupInfo = new StreamGroupInfoProcessor(); + + public static readonly StreamInfoProcessor + StreamInfo = new StreamInfoProcessor(); + + public static readonly StreamPendingInfoProcessor + StreamPendingInfo = new StreamPendingInfoProcessor(); + + public static readonly StreamPendingMessagesProcessor + StreamPendingMessages = new StreamPendingMessagesProcessor(); + public static ResultProcessor GeoRadiusArray(GeoRadiusOptions options) => GeoRadiusResultArrayProcessor.Get(options); public static readonly ResultProcessor @@ -1301,6 +1325,460 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes } } + internal sealed class SingleStreamProcessor : StreamProcessorBase + { + private bool skipStreamName; + + public SingleStreamProcessor(bool skipStreamName = false) + { + this.skipStreamName = skipStreamName; + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) + { + if (result.IsNull) + { + // Server returns 'nil' if no entries are returned for the given stream. + SetResult(message, new RedisStreamEntry[0]); + return true; + } + + if (result.Type != ResultType.MultiBulk) + { + return false; + } + + if (skipStreamName) + { + // Possibly validate that the command was XREAD? XREAD is the only + // command that should use this option. + + // Skip the first element in the array (i.e., the stream name). + // See https://redis.io/commands/xread. + + // > XREAD COUNT 2 STREAMS mystream 0 + // 1) 1) "mystream" <== Skip the stream name + // 2) 1) 1) 1519073278252 - 0 <== Index 1 contains the array of messages + // 2) 1) "foo" + // 2) "value_1" + // 2) 1) 1519073279157 - 0 + // 2) 1) "foo" + // 2) "value_2" + + result = result.GetItems()[0] // Initial result array, then the first stream. + .GetItems()[1]; // Skip the stream name and return the array of stream entries. + } + + var entries = ParseRedisStreamEntries(result); + + SetResult(message, entries); + return true; + } + } + + internal sealed class MultiStreamProcessor : StreamProcessorBase + { + /* + + The result is similar to the XRANGE result (see SingleStreamProcessor) + with the addition of the stream name as the first element of top level + Multibulk array. + + See https://redis.io/commands/xread. + + > XREAD COUNT 2 STREAMS mystream writers 0-0 0-0 + 1) 1) "mystream" + 2) 1) 1) 1526984818136-0 + 2) 1) "duration" + 2) "1532" + 3) "event-id" + 4) "5" + 2) 1) 1526999352406-0 + 2) 1) "duration" + 2) "812" + 3) "event-id" + 4) "9" + 2) 1) "writers" + 2) 1) 1) 1526985676425-0 + 2) 1) "name" + 2) "Virginia" + 3) "surname" + 4) "Woolf" + 2) 1) 1526985685298-0 + 2) 1) "name" + 2) "Jane" + 3) "surname" + 4) "Austen" + */ + + protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) + { + if (result.IsNull) + { + // Nothing returned for any of the requested streams. The server returns 'nil'. + SetResult(message, new RedisStream[0]); + return true; + } + + if (result.Type != ResultType.MultiBulk) + { + return false; + } + + var arr = result.GetItems(); + + var streams = new RedisStream[arr.Length]; + + for (var i = 0; i < arr.Length; i++) + { + if (arr[i].Type != ResultType.MultiBulk) + { + return false; + } + + var stream = arr[i].GetItems(); + + // stream[0] = Name of the Stream + // stream[1] = Multibulk Array of Stream Entries + + var streamName = stream[0].AsRedisKey(); + var streamEntries = ParseRedisStreamEntries(stream[1]); + + streams[i] = new RedisStream(streamName, streamEntries); + } + + SetResult(message, streams); + return true; + } + } + + internal sealed class StreamConsumerInfoProcessor : InterleavedStreamInfoProcessorBase + { + protected override StreamConsumerInfo ParseItem(RawResult result) + { + // Note: the base class passes a single consumer from the response into this method. + + // Response format: + // > XINFO CONSUMERS mystream mygroup + // 1) 1) name + // 2) "Alice" + // 3) pending + // 4) (integer)1 + // 5) idle + // 6) (integer)9104628 + // 2) 1) name + // 2) "Bob" + // 3) pending + // 4) (integer)1 + // 5) idle + // 6) (integer)83841983 + + var arr = result.GetItems(); + + var consumerName = (string)arr[1].AsRedisValue(); + var pendingCount = (int)arr[3].AsRedisValue(); + var idleTime = (int)arr[5].AsRedisValue(); + + return new StreamConsumerInfo(consumerName, pendingCount, idleTime); + } + } + + internal sealed class StreamGroupInfoProcessor : InterleavedStreamInfoProcessorBase + { + protected override StreamGroupInfo ParseItem(RawResult result) + { + // Note: the base class passes a single item from the response into this method. + + // Response format: + // > XINFO GROUPS mystream + // 1) 1) name + // 2) "mygroup" + // 3) consumers + // 4) (integer)2 + // 5) pending + // 6) (integer)2 + // 2) 1) name + // 2) "some-other-group" + // 3) consumers + // 4) (integer)1 + // 5) pending + // 6) (integer)0 + + var arr = result.GetItems(); + + var groupName = (string)arr[1].AsRedisValue(); + var consumerCount = (int)arr[3].AsRedisValue(); + var pendingCount = (int)arr[5].AsRedisValue(); + + return new StreamGroupInfo(groupName, consumerCount, pendingCount); + } + } + + internal abstract class InterleavedStreamInfoProcessorBase : ResultProcessor + { + protected abstract T ParseItem(RawResult result); + + protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) + { + if (result.Type != ResultType.MultiBulk) + { + return false; + } + + var arr = result.GetItems(); + var parsedItems = new T[arr.Length]; + + for (var i = 0; i < arr.Length; i++) + { + parsedItems[i] = ParseItem(arr[i]); + } + + SetResult(message, parsedItems); + return true; + } + } + + internal sealed class StreamInfoProcessor : StreamProcessorBase + { + // Parse the following format: + // > XINFO mystream + // 1) length + // 2) (integer) 13 + // 3) radix-tree-keys + // 4) (integer) 1 + // 5) radix-tree-nodes + // 6) (integer) 2 + // 7) groups + // 8) (integer) 2 + // 9) first-entry + // 10) 1) 1524494395530-0 + // 2) 1) "a" + // 2) "1" + // 3) "b" + // 4) "2" + // 11) last-entry + // 12) 1) 1526569544280-0 + // 2) 1) "message" + // 2) "banana" + protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) + { + if (result.Type != ResultType.MultiBulk) + { + return false; + } + + var arr = result.GetItems(); + + if (arr.Length != 12) + { + return false; + } + + // The first four items are interleaved name/value pairs. + // Skip the field name and just get the value. + + var length = (int)arr[1].AsRedisValue(); + var treeKeyCount = (int)arr[3].AsRedisValue(); + var treeNodeCount = (int)arr[5].AsRedisValue(); + var groupCount = (int)arr[7].AsRedisValue(); + + // Note: Even if there is only 1 message in the stream, this command returns + // the single entry as the first-entry and last-entry in the response. + + var entries = ParseRedisStreamEntries(new RawResult(new RawResult[] + { + arr[9], + arr[11] + })); + + var streamInfo = new StreamInfo(length, + treeKeyCount, + treeNodeCount, + groupCount, + entries[0], + entries[1]); + + SetResult(message, streamInfo); + return true; + } + } + + internal sealed class StreamPendingInfoProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) + { + // Example: + // > XPENDING mystream mygroup + // 1) (integer)2 + // 2) 1526569498055 - 0 + // 3) 1526569506935 - 0 + // 4) 1) 1) "Bob" + // 2) "2" + + if (result.Type != ResultType.MultiBulk) + { + return false; + } + + var arr = result.GetItems(); + + if (arr.Length != 4) + { + return false; + } + + var pendingMessageCount = arr[0].AsRedisValue(); + var lowestPendingMessageId = arr[1].AsRedisValue(); + var highestPendingMessageId = arr[2].AsRedisValue(); + + // Get the list of Consumers + arr = arr[3].GetItems(); + + var consumers = new StreamConsumer[arr.Length]; + + for (var i = 0; i < arr.Length; i++) + { + var details = arr[i].GetItems(); + + consumers[i] = new StreamConsumer + { + Name = details[0].AsRedisValue(), + PendingMessageCount = details[1].AsRedisValue() + }; + } + + var pendingInfo = new StreamPendingInfo(pendingMessageCount, + lowestPendingMessageId, + highestPendingMessageId, + consumers); + + SetResult(message, pendingInfo); + return true; + } + } + + internal sealed class StreamPendingMessagesProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) + { + if (result.Type != ResultType.MultiBulk) + { + return false; + } + + var arr = result.GetItems(); + + var messages = new StreamPendingMessageInfo[arr.Length]; + + for (var i = 0; i < arr.Length; i++) + { + var details = arr[i].GetItems(); + + var messageId = details[0].AsRedisValue(); + var consumerName = details[1].AsRedisValue(); + var idleTimeInMs = details[2].AsRedisValue(); + var deliveryCount = details[3].AsRedisValue(); + + messages[i] = new StreamPendingMessageInfo(messageId, + consumerName, + idleTimeInMs, + deliveryCount); + } + + SetResult(message, messages); + return true; + } + } + + internal abstract class StreamProcessorBase : ResultProcessor + { + // For command response formats see https://redis.io/topics/streams-intro. + + protected RedisStreamEntry[] ParseRedisStreamEntries(RawResult result) + { + if (result.Type != ResultType.MultiBulk) + { + return null; + } + + var arr = result.GetItems(); + + var entries = new RedisStreamEntry[arr.Length]; + + for (var i = 0; i < arr.Length; i++) + { + if (arr[i].Type != ResultType.MultiBulk) + { + return null; + } + + // Process the Multibulk array for each entry. The entry contains the following elements: + // [0] = SimpleString (the ID of the stream entry) + // [1] = Multibulk array of the name/value pairs of the stream entry's data + var item = arr[i].GetItems(); + + var id = item[0].AsRedisValue(); + var values = ParseStreamEntryValues(item[1]); + + entries[i] = new RedisStreamEntry(id, values); + } + + return entries; + } + + protected NameValueEntry[] ParseStreamEntryValues(RawResult result) + { + // The XRANGE, XREVRANGE, XREAD commands return stream entries + // in the following format. The name/value pairs are interleaved + // in the same fashion as the HGETALL response. + // + // 1) 1) 1518951480106-0 + // 2) 1) "sensor-id" + // 2) "1234" + // 3) "temperature" + // 4) "19.8" + // 2) 1) 1518951482479-0 + // 2) 1) "sensor-id" + // 2) "9999" + // 3) "temperature" + // 4) "18.2" + + + if (result.Type != ResultType.MultiBulk) + { + return null; + } + + var arr = result.GetItems(); + + if (arr == null) + { + return null; + } + + NameValueEntry[] pairs; + + int count = arr.Length / 2; + + if (count == 0) + { + pairs = new NameValueEntry[0]; + } + else + { + pairs = new NameValueEntry[count]; + int offset = 0; + for (int i = 0; i < pairs.Length; i++) + { + pairs[i] = new NameValueEntry(arr[offset++].AsRedisValue(), + arr[offset++].AsRedisValue()); + } + } + + return pairs; + } + } + private sealed class StringPairInterleavedProcessor : ValuePairInterleavedProcessorBase> { protected override KeyValuePair Parse(RawResult first, RawResult second) diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs b/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs new file mode 100644 index 000000000..293a32132 --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs @@ -0,0 +1,59 @@ + +namespace StackExchange.Redis +{ + /// + /// Constants representing values used in Redis Stream commands. + /// + public static class StreamConstants + { + /// + /// The "~" value used with the MAXLEN option. + /// + public static readonly RedisValue ApproximateMaxLen = "~"; + + /// + /// The "*" value used with the XADD command. + /// + public static readonly RedisValue AutoGeneratedId = "*"; + + /// + /// The "$" value used in the XGROUP command. Indicates reading only new messages from the stream. + /// + public static readonly RedisValue NewMessages = "$"; + + /// + /// The "-" value used in the XRANGE, XREAD, and XREADGROUP commands. Indicates the minimum message ID from the stream. + /// + public static readonly RedisValue StreamMinValue = "-"; + + /// + /// The "+" value used in the XRANGE, XREAD, and XREADGROUP commands. Indicates the maximum message ID from the stream. + /// + public static readonly RedisValue StreamMaxValue = "+"; + + /// + /// The ">" value used in the XREADGROUP command. Use this to read messages that have not been delivered to a consumer group. + /// + public static readonly RedisValue UndeliveredMessages = ">"; + + internal static readonly RedisValue Blank = ""; + + internal static readonly RedisValue Consumers = "CONSUMERS"; + + internal static readonly RedisValue Count = "COUNT"; + + internal static readonly RedisValue Create = "CREATE"; + + internal static readonly RedisValue Group = "GROUP"; + + internal static readonly RedisValue Groups = "GROUPS"; + + internal static readonly RedisValue JustId = "JUSTID"; + + internal static readonly RedisValue MaxLen = "MAXLEN"; + + internal static readonly RedisValue Stream = "STREAM"; + + internal static readonly RedisValue Streams = "STREAMS"; + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs b/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs new file mode 100644 index 000000000..006b2666f --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs @@ -0,0 +1,19 @@ + +namespace StackExchange.Redis +{ + /// + /// Describes a consumer off a Redis Stream. + /// + public class StreamConsumer + { + /// + /// The name of the consumer. + /// + public RedisValue Name { get; set; } + + /// + /// The number of messages that have been delivered by not yet acknowledged by the consumer. + /// + public RedisValue PendingMessageCount { get; set; } + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs new file mode 100644 index 000000000..abfee2309 --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs @@ -0,0 +1,32 @@ + +namespace StackExchange.Redis +{ + /// + /// Describes a consumer within a consumer group, retrieved using the XINFO CONSUMERS command. + /// + public class StreamConsumerInfo + { + internal StreamConsumerInfo(string name, int pendingMessageCount, long idleTimeInMilliseconds) + { + Name = name; + PendingMessageCount = pendingMessageCount; + IdleTimeInMilliseconds = idleTimeInMilliseconds; + } + + /// + /// The name of the consumer. + /// + public string Name { get; } + + /// + /// The number of pending messages for the consumer. A pending message is one that has been + /// received by the consumer but not yet acknowledged. + /// + public int PendingMessageCount { get; } + + /// + /// The idle time, if any, for the consumer. + /// + public long IdleTimeInMilliseconds { get; } + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs new file mode 100644 index 000000000..aea71cc33 --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs @@ -0,0 +1,32 @@ + +namespace StackExchange.Redis +{ + /// + /// Describes a consumer group retrieved using the XINFO GROUPS command. + /// + public class StreamGroupInfo + { + internal StreamGroupInfo(string name, int consumerCount, int pendingMessageCount) + { + Name = name; + ConsumerCount = consumerCount; + PendingMessageCount = pendingMessageCount; + } + + /// + /// The name of the consumer group. + /// + public string Name { get; } + + /// + /// The number of consumers within the consumer group. + /// + public int ConsumerCount { get; } + + /// + /// The total number of pending messages for the consumer group. A pending message is one that has been + /// received by a consumer but not yet acknowledged. + /// + public int PendingMessageCount { get; } + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs new file mode 100644 index 000000000..21d8c6c11 --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs @@ -0,0 +1,54 @@ + +namespace StackExchange.Redis +{ + /// + /// Describes stream information retrieved using the XINFO STREAM command. + /// + public class StreamInfo + { + internal StreamInfo(int length, + int radixTreeKeys, + int radixTreeNodes, + int groups, + RedisStreamEntry firstEntry, + RedisStreamEntry lastEntry) + { + Length = length; + RadixTreeKeys = radixTreeKeys; + RadixTreeNodes = radixTreeNodes; + ConsumerGroupCount = groups; + FirstEntry = firstEntry; + LastEntry = lastEntry; + } + + /// + /// The number of entries in the stream. + /// + public int Length { get; } + + /// + /// The number of radix tree keys in the stream. + /// + public int RadixTreeKeys { get; } + + /// + /// The number of radix tree nodes in the stream. + /// + public int RadixTreeNodes { get; } + + /// + /// The number of consumers groups in the stream. + /// + public int ConsumerGroupCount { get; } + + /// + /// The first entry in the stream. + /// + public RedisStreamEntry FirstEntry { get; } + + /// + /// The last entry in the stream. + /// + public RedisStreamEntry LastEntry { get; } + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs new file mode 100644 index 000000000..ecbf29d3f --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs @@ -0,0 +1,41 @@ + +namespace StackExchange.Redis +{ + /// + /// + /// + public class StreamPendingInfo + { + internal StreamPendingInfo(RedisValue pendingMessageCount, + RedisValue lowestId, + RedisValue highestId, + StreamConsumer[] consumers) + { + PendingMessageCount = pendingMessageCount; + LowestPendingMessageId = lowestId; + HighestPendingMessageId = highestId; + Consumers = consumers; + } + + /// + /// + /// + public RedisValue PendingMessageCount { get; } + + /// + /// + /// + public RedisValue LowestPendingMessageId { get; } + + /// + /// + /// + public RedisValue HighestPendingMessageId { get; } + + /// + /// + /// + public StreamConsumer[] Consumers { get; } + + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs new file mode 100644 index 000000000..926e98be6 --- /dev/null +++ b/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs @@ -0,0 +1,43 @@ + +namespace StackExchange.Redis +{ + /// + /// + /// + public class StreamPendingMessageInfo + { + /// + /// + /// + internal StreamPendingMessageInfo(RedisValue messageId, + RedisValue consumerName, + RedisValue idleTimeInMs, + RedisValue deliveryCount) + { + MessageId = messageId; + ConsumerName = consumerName; + IdleTimeInMilliseconds = idleTimeInMs; + DeliveryCount = deliveryCount; + } + + /// + /// + /// + public RedisValue MessageId { get; } + + /// + /// + /// + public RedisValue ConsumerName { get; } + + /// + /// + /// + public RedisValue IdleTimeInMilliseconds { get; } + + /// + /// + /// + public RedisValue DeliveryCount { get; } + } +} From ef4657a543563dec2d1fa9bed091547c98161b93 Mon Sep 17 00:00:00 2001 From: ttingen Date: Sun, 17 Jun 2018 17:37:41 -0400 Subject: [PATCH 02/15] Redis Streams cleanup. - Cleaned up some comments in the test suite. - Added two new tests (StreamReadMultipleStreamsWithCount & StreamConsumerGroupClaimMessagesReturningIds). - Updated the XML docs on NameValueEntry to remove the old references to HashEntry. --- StackExchange.Redis.Tests/Streams.cs | 127 +++++++++++++++--- .../StackExchange/Redis/NameValueEntry.cs | 11 +- .../StackExchange/Redis/RedisDatabase.cs | 35 +++++ 3 files changed, 145 insertions(+), 28 deletions(-) diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index bb7c224de..3a3240f5d 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -167,6 +167,7 @@ public void StreamConsumerGroupReadFromStreamBeginningWithCount() // Start reading after id1. var entries = db.StreamReadGroup(key, groupName, "test_consumer", id1, 2); + // Ensure we only received the requested count and that the ID's match the expected values. Assert.True(entries.Length == 2); Assert.True(id2 == entries[0].Id); Assert.True(id3 == entries[1].Id); @@ -191,7 +192,6 @@ public void StreamConsumerGroupAcknowledgeMessage() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - // Read from the beginning of the stream. db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); // Read all 4 messages, they will be assigned to the consumer @@ -231,7 +231,6 @@ public void StreamConsumerGroupClaimMessages() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - // Read from the beginning of the stream. db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); // Read a single message into the first consumer. @@ -242,14 +241,14 @@ public void StreamConsumerGroupClaimMessages() // Claim the 3 messages consumed by consumer2 for consumer1. - // Get the pending messages for consumer2 + // Get the pending messages for consumer2. var pendingMessages = db.StreamPendingMessageInfoGet(key, groupName, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue, 10, consumer2); - // Claim the messages for consumer1 + // Claim the messages for consumer1. var messages = db.StreamClaimMessages(key, groupName, consumer1, @@ -266,6 +265,58 @@ public void StreamConsumerGroupClaimMessages() } } + [Fact] + public void StreamConsumerGroupClaimMessagesReturningIds() + { + var key = GetUniqueKey("group_claim_view_ids"); + var groupName = "test_group"; + var consumer1 = "test_consumer_1"; + var consumer2 = "test_consumer_2"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + + // Read a single message into the first consumer. + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.StreamMinValue, 1); + + // Read the remaining messages into the second consumer. + var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + + // Claim the 3 messages consumed by consumer2 for consumer1. + + // Get the pending messages for consumer2. + var pendingMessages = db.StreamPendingMessageInfoGet(key, groupName, + StreamConstants.StreamMinValue, + StreamConstants.StreamMaxValue, + 10, + consumer2); + + // Claim the messages for consumer1. + var messageIds = db.StreamClaimMessagesReturningIds(key, + groupName, + consumer1, + 0, // Min message idle time + messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray().ToStringArray()); + + // We should get an array of 3 message ID's. + Assert.Equal(3, messageIds.Length); + Assert.Equal(id2, messageIds[0]); + Assert.Equal(id3, messageIds[1]); + Assert.Equal(id4, messageIds[2]); + } + + } + [Fact] public void StreamConsumerGroupViewPendingInfoSummary() { @@ -285,7 +336,6 @@ public void StreamConsumerGroupViewPendingInfoSummary() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - // Read from the beginning of the stream. db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); // Read a single message into the first consumer. @@ -329,7 +379,6 @@ public void StreamConsumerGroupViewPendingMessageInfo() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - // Read from the beginning of the stream. db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); // Read a single message into the first consumer. @@ -369,7 +418,6 @@ public void StreamConsumerGroupViewPendingMessageInfoForConsumer() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - // Read from the beginning of the stream. db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); // Read a single message into the first consumer. @@ -435,7 +483,6 @@ public void StreamGroupInfoGet() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - // Read from the beginning of the stream. db.StreamCreateConsumerGroup(key, group1, StreamConstants.StreamMinValue); db.StreamCreateConsumerGroup(key, group2, StreamConstants.StreamMinValue); @@ -477,7 +524,6 @@ public void StreamGroupConsumerInfoGet() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - // Read from the beginning of the stream. db.StreamCreateConsumerGroup(key, group, StreamConstants.StreamMinValue); db.StreamReadGroup(key, group, consumer1, StreamConstants.UndeliveredMessages, 1); db.StreamReadGroup(key, group, consumer2, StreamConstants.UndeliveredMessages); @@ -554,7 +600,6 @@ public void StreamReadRange() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); @@ -577,7 +622,6 @@ public void StreamReadRangeWithCount() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); @@ -599,7 +643,6 @@ public void StreamReadRangeReverse() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); @@ -622,7 +665,6 @@ public void StreamReadRangeReverseWithCount() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); @@ -644,11 +686,11 @@ public void StreamRead() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); var id3 = db.StreamAdd(key, "field3", "value3"); + // Read the entire stream from the beginning. var entries = db.StreamRead(key, "0-0"); Assert.True(entries.Length == 3); @@ -669,11 +711,11 @@ public void StreamReadWithAfterIdAndCount_1() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); var id3 = db.StreamAdd(key, "field3", "value3"); + // Only read a single item from the stream. var entries = db.StreamRead(key, id1, 1); Assert.True(entries.Length == 1); @@ -692,12 +734,12 @@ public void StreamReadWithAfterIdAndCount_2() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); + // Read multiple items from the stream. var entries = db.StreamRead(key, id1, 2); Assert.True(entries.Length == 2); @@ -717,10 +759,11 @@ public void StreamReadPastEndOfStream() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); + // Read after the final ID in the stream, we expect an empty array as a response. + var entries = db.StreamRead(key, id2); Assert.True(entries.Length == 0); @@ -739,12 +782,12 @@ public void StreamReadMultipleStreams() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key1, "field1", "value1"); var id2 = db.StreamAdd(key1, "fiedl2", "value2"); var id3 = db.StreamAdd(key2, "field3", "value3"); var id4 = db.StreamAdd(key2, "field4", "value4"); + // Read from both streams at the same time. var streamList = new KeyValuePair[2] { new KeyValuePair(key1, "0-0"), @@ -763,6 +806,44 @@ public void StreamReadMultipleStreams() } } + [Fact] + public void StreamReadMultipleStreamsWithCount() + { + var key1 = GetUniqueKey("read_multi_count_1"); + var key2 = GetUniqueKey("read_multi_count_2"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key1, "field1", "value1"); + var id2 = db.StreamAdd(key1, "fiedl2", "value2"); + var id3 = db.StreamAdd(key2, "field3", "value3"); + var id4 = db.StreamAdd(key2, "field4", "value4"); + + var streamList = new KeyValuePair[2] + { + new KeyValuePair(key1, "0-0"), + new KeyValuePair(key2, "0-0") + }; + + var streams = db.StreamRead(streamList, countPerStream: 1); + + // We should get both streams back. + Assert.True(streams.Length == 2); + + // Ensure we only got one message per stream. + Assert.True(streams[0].Entries.Length == 1); + Assert.True(streams[1].Entries.Length == 1); + + // Check the message ID's as well. + Assert.Equal(id1, streams[0].Entries[0].Id); + Assert.Equal(id3, streams[1].Entries[0].Id); + } + } + [Fact] public void StreamReadMultipleStreamsWithReadPastSecondStream() { @@ -775,7 +856,6 @@ public void StreamReadMultipleStreamsWithReadPastSecondStream() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key1, "field1", "value1"); var id2 = db.StreamAdd(key1, "fiedl2", "value2"); var id3 = db.StreamAdd(key2, "field3", "value3"); @@ -784,7 +864,9 @@ public void StreamReadMultipleStreamsWithReadPastSecondStream() var streamList = new KeyValuePair[2] { new KeyValuePair(key1, "0-0"), - new KeyValuePair(key2, id4) // read past the end of stream # 2 + + // read past the end of stream # 2 + new KeyValuePair(key2, id4) }; var streams = db.StreamRead(streamList); @@ -809,7 +891,6 @@ public void StreamReadMultipleStreamsWithEmptyResponse() var db = conn.GetDatabase(); - // Add a couple items and check length. var id1 = db.StreamAdd(key1, "field1", "value1"); var id2 = db.StreamAdd(key1, "fiedl2", "value2"); var id3 = db.StreamAdd(key2, "field3", "value3"); @@ -817,12 +898,14 @@ public void StreamReadMultipleStreamsWithEmptyResponse() var streamList = new KeyValuePair[2] { + // Read past the end of both streams. new KeyValuePair(key1, id2), - new KeyValuePair(key2, id4) // read past the end of stream # 2 + new KeyValuePair(key2, id4) }; var streams = db.StreamRead(streamList); + // We expect an empty response. Assert.True(streams.Length == 0); } } diff --git a/StackExchange.Redis/StackExchange/Redis/NameValueEntry.cs b/StackExchange.Redis/StackExchange/Redis/NameValueEntry.cs index 17268d5b4..ea091112b 100644 --- a/StackExchange.Redis/StackExchange/Redis/NameValueEntry.cs +++ b/StackExchange.Redis/StackExchange/Redis/NameValueEntry.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; namespace StackExchange.Redis { @@ -14,8 +13,8 @@ public struct NameValueEntry : IEquatable /// /// Initializes a value. /// - /// The name for this hash entry. - /// The value for this hash entry. + /// The name for this entry. + /// The value for this entry. public NameValueEntry(RedisValue name, RedisValue value) { this.name = name; @@ -23,12 +22,12 @@ public NameValueEntry(RedisValue name, RedisValue value) } /// - /// The name of the hash field + /// The name of the field. /// public RedisValue Name => name; /// - /// The value of the hash field + /// The value of the field. /// public RedisValue Value => value; @@ -42,7 +41,7 @@ public static implicit operator KeyValuePair(NameValueEn /// /// Converts from a key/value pair /// - /// The to get a from. + /// The to get a from. public static implicit operator NameValueEntry(KeyValuePair value) => new NameValueEntry(value.Key, value.Value); diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index 1b8f2de6c..b43a3eef0 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -2033,6 +2033,11 @@ public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, CommandFl public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) { + if (count <= 0) + { + throw new ArgumentException("'count' must be greater than 0."); + } + // Example: > XREAD COUNT 2 STREAMS writers 1526999352406-0 var msg = Message.Create(Database, flags, @@ -2057,6 +2062,11 @@ public RedisStream[] StreamRead(IList> stream public RedisStream[] StreamRead(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) { + if (countPerStream <= 0) + { + throw new ArgumentException("'countPerStream' must be greater than 0."); + } + var msg = GetMultiStreamReadMessage(streamWithAfterIdList, countPerStream, flags); return ExecuteSync(msg, ResultProcessor.MultiStream); } @@ -2078,6 +2088,11 @@ public Task StreamReadAsync(RedisKey key, RedisValue afterId public Task StreamReadAsync(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) { + if (count <= 0) + { + throw new ArgumentException("'count' must be greater than 0."); + } + var msg = Message.Create(Database, flags, RedisCommand.XREAD, @@ -2101,6 +2116,11 @@ public Task StreamReadAsync(IList StreamReadAsync(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) { + if (countPerStream <= 0) + { + throw new ArgumentException("'countPerStream' must be greater than 0."); + } + var msg = GetMultiStreamReadMessage(streamWithAfterIdList, countPerStream, flags); return ExecuteAsync(msg, ResultProcessor.MultiStream); } @@ -2113,12 +2133,22 @@ public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, Re public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) { + if (count <= 0) + { + throw new ArgumentException("'count' must be greater than 0."); + } + var msg = GetStreamReadGroupMessage(key, groupName, consumerName, readFromId, count, flags); return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip); } public RedisValue StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { + if (maxLength <= 0) + { + throw new ArgumentException("'maxLength' must be greater than 0."); + } + var msg = Message.Create(Database, flags, RedisCommand.XTRIM, @@ -2132,6 +2162,11 @@ public RedisValue StreamTrim(RedisKey key, int maxLength, bool useApproximateMax public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { + if (maxLength <= 0) + { + throw new ArgumentException("'maxLength' must be greater than 0."); + } + var msg = Message.Create(Database, flags, RedisCommand.XTRIM, From 80c1ebe0a57564b9257817a3f50ac3e885139515 Mon Sep 17 00:00:00 2001 From: ttingen Date: Sun, 17 Jun 2018 20:56:44 -0400 Subject: [PATCH 03/15] Renamed a unit test and added another one for one of the StreamAdd overloads. --- StackExchange.Redis.Tests/Streams.cs | 34 ++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index 3a3240f5d..6ce86339a 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -25,15 +25,13 @@ public void StreamAddSinglePairWithAutoId() } [Fact] - public void StreamAddMultipleValuePairsWithManualId() + public void StreamAddMultipleValuePairsWithAutoId() { - var id = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}-0"; - using (var conn = Create()) { Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); - var key = GetUniqueKey("manual_id"); + var key = GetUniqueKey("multiple_value_pairs"); var fields = new NameValueEntry[2] { @@ -72,6 +70,34 @@ public void StreamAddWithManualId() } } + [Fact] + public void StreamAddMultipleValuePairsWithManualId() + { + var id = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}-0"; + var key = GetUniqueKey("manual_id_multiple_values"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var fields = new NameValueEntry[2] + { + new NameValueEntry("field1", "value1"), + new NameValueEntry("field2", "value2") + }; + + var messageId = db.StreamAdd(key, id, fields); + var entries = db.StreamRange(key, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue); + + Assert.Equal(id, messageId); + Assert.NotNull(entries); + Assert.True(entries.Length == 1); + Assert.Equal(id, entries[0].Id); + } + } + [Fact] public void StreamCreateConsumerGroup() { From 2553877d270d93fee76556417a1c5dd5d9dd0759 Mon Sep 17 00:00:00 2001 From: ttingen Date: Mon, 18 Jun 2018 21:18:22 -0400 Subject: [PATCH 04/15] Added documentation to the StreamPendingInfo and StreamPendingMessageInfo classes. --- .../StackExchange/Redis/StreamPendingInfo.cs | 10 +++++----- .../Redis/StreamPendingMessageInfo.cs | 14 ++++++-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs index ecbf29d3f..779affea0 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs @@ -2,7 +2,7 @@ namespace StackExchange.Redis { /// - /// + /// Describes basic information about pending messages for a consumer group. /// public class StreamPendingInfo { @@ -18,22 +18,22 @@ internal StreamPendingInfo(RedisValue pendingMessageCount, } /// - /// + /// The number of pending messages. A pending message is a message that has been consumed but not yet acknowledged. /// public RedisValue PendingMessageCount { get; } /// - /// + /// The lowest message ID in the set of pending messages. /// public RedisValue LowestPendingMessageId { get; } /// - /// + /// The highest message ID in the set of pending messages. /// public RedisValue HighestPendingMessageId { get; } /// - /// + /// An array of consumers within the consumer group that have pending messages. /// public StreamConsumer[] Consumers { get; } diff --git a/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs index 926e98be6..50b68f489 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs @@ -2,13 +2,11 @@ namespace StackExchange.Redis { /// - /// + /// Describes properties of a pending message. A pending message is one that has + /// been received by a consumer but has not yet been acknowledged. /// public class StreamPendingMessageInfo { - /// - /// - /// internal StreamPendingMessageInfo(RedisValue messageId, RedisValue consumerName, RedisValue idleTimeInMs, @@ -21,22 +19,22 @@ internal StreamPendingMessageInfo(RedisValue messageId, } /// - /// + /// The ID of the pending message. /// public RedisValue MessageId { get; } /// - /// + /// The consumer that received the pending message. /// public RedisValue ConsumerName { get; } /// - /// + /// The time that has passed since the message was last delivered to a consumer. /// public RedisValue IdleTimeInMilliseconds { get; } /// - /// + /// The number of times the message has been delivered to a consumer. /// public RedisValue DeliveryCount { get; } } From 0140c35b9d63ff4d7d1703380260459446d4b1cf Mon Sep 17 00:00:00 2001 From: ttingen Date: Thu, 21 Jun 2018 20:47:14 -0400 Subject: [PATCH 05/15] Changed the StreamAcknowledge method to return long instead of RedisValue, makes it more intuitive. --- StackExchange.Redis.Tests/Streams.cs | 4 ++++ .../StackExchange/Redis/Interfaces/IDatabase.cs | 4 ++-- .../Redis/Interfaces/IDatabaseAsync.cs | 4 ++-- .../Redis/KeyspaceIsolation/DatabaseWrapper.cs | 4 ++-- .../Redis/KeyspaceIsolation/WrapperBase.cs | 4 ++-- .../StackExchange/Redis/RedisDatabase.cs | 16 ++++++++-------- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index 6ce86339a..0b448c502 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -224,7 +224,11 @@ public void StreamConsumerGroupAcknowledgeMessage() var entries = db.StreamReadGroup(key, groupName, consumer, "0-0"); // Send XACK for 3 of the messages + + // Single message Id overload. var oneAck = db.StreamAcknowledge(key, groupName, id1); + + // Multiple message Id overload. var twoAck = db.StreamAcknowledge(key, groupName, new string[] { id3, id4 }); // Read the group again, it should only return the unacknowledged message. diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs index f28f4e314..aaf15e4ba 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs @@ -1398,7 +1398,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro - RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None); + long StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None); /// /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. @@ -1409,7 +1409,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro - RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None); + long StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs index a360422ce..da908a84a 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs @@ -1308,7 +1308,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro - Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None); + Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None); /// /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. @@ -1319,7 +1319,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro - Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None); + Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs index b1eb12214..c99f368b4 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -587,12 +587,12 @@ public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue return Inner.SortedSetScore(ToInner(key), member, flags); } - public RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + public long StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) { return Inner.StreamAcknowledge(ToInner(key), groupName, messageId, flags); } - public RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + public long StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamAcknowledge(ToInner(key), groupName, messageIds, flags); } diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs index f92555498..d354a7bf0 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs @@ -566,12 +566,12 @@ public Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, return Inner.SortedSetScoreAsync(ToInner(key), member, flags); } - public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) { return Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageId, flags); } - public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageIds, flags); } diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index b43a3eef0..54b5841fd 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -1579,28 +1579,28 @@ IEnumerable IDatabase.SortedSetScan(RedisKey key, RedisValue pat return ExecuteAsync(msg, ResultProcessor.NullableDouble); } - public RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + public long StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageId); - return ExecuteSync(msg, ResultProcessor.RedisValue); + return ExecuteSync(msg, ResultProcessor.Int64); } - public RedisValue StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + public long StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageIds); - return ExecuteSync(msg, ResultProcessor.RedisValue); + return ExecuteSync(msg, ResultProcessor.Int64); } - public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageId); - return ExecuteAsync(msg, ResultProcessor.RedisValue); + return ExecuteAsync(msg, ResultProcessor.Int64); } - public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageIds); - return ExecuteAsync(msg, ResultProcessor.RedisValue); + return ExecuteAsync(msg, ResultProcessor.Int64); } public string StreamAdd(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) From 8f8e2d112cfd8076c0db377aa251a7256219738f Mon Sep 17 00:00:00 2001 From: ttingen Date: Sat, 23 Jun 2018 19:41:01 -0400 Subject: [PATCH 06/15] Added a Null value for RedisStreamEntry and an additional test for StreamInfoGet. StreamInfoGet can return a null value for FirstEntry and LastEntry if the stream has no entries. --- StackExchange.Redis.Tests/Streams.cs | 27 +++++++++++++++++++ .../StackExchange/Redis/RedisStreamEntry.cs | 10 +++++++ .../StackExchange/Redis/ResultProcessor.cs | 27 ++++++++++--------- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index 0b448c502..fae7bf41f 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -598,6 +598,33 @@ public void StreamInfoGet() } } + [Fact] + public void StreamInfoGetWithEmptyStream() + { + var key = GetUniqueKey("stream_info_empty"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add an entry and then delete it so the stream is empty, then run streaminfo + // to ensure it functions properly on an empty stream. Namely, the first-entry + // and last-entry messages should be null. + + var id = db.StreamAdd(key, "field1", "value1"); + db.StreamMessagesDelete(key, new string[1] { id }); + + Assert.Equal(0, db.StreamLength(key)); + + var streamInfo = db.StreamInfoGet(key); + + Assert.True(streamInfo.FirstEntry.IsNull); + Assert.True(streamInfo.LastEntry.IsNull); + } + } + [Fact] public void StreamVerifyLength() { diff --git a/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs b/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs index 5be33a1c9..f3c59a64f 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs @@ -19,6 +19,11 @@ public RedisStreamEntry(RedisValue id, NameValueEntry[] values) this.values = values; } + /// + /// A null stream entry. + /// + public static RedisStreamEntry Null { get; } = new RedisStreamEntry(RedisValue.Null, null); + /// /// The ID assigned to the message. /// @@ -28,5 +33,10 @@ public RedisStreamEntry(RedisValue id, NameValueEntry[] values) /// The values contained within the message. /// public NameValueEntry[] Values => values; + + /// + /// Indicates that the Redis Stream Entry is null. + /// + public bool IsNull => id == RedisValue.Null && values == null; } } diff --git a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs b/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs index 2eb7b0b84..f14a35a2c 100644 --- a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs +++ b/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs @@ -1552,13 +1552,13 @@ internal sealed class StreamInfoProcessor : StreamProcessorBase // 8) (integer) 2 // 9) first-entry // 10) 1) 1524494395530-0 - // 2) 1) "a" + // 2) 1) "a" // 2) "1" // 3) "b" // 4) "2" // 11) last-entry // 12) 1) 1526569544280-0 - // 2) 1) "message" + // 2) 1) "message" // 2) "banana" protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) { @@ -1707,22 +1707,23 @@ protected RedisStreamEntry[] ParseRedisStreamEntries(RawResult result) for (var i = 0; i < arr.Length; i++) { - if (arr[i].Type != ResultType.MultiBulk) + if (arr[i].IsNull || arr[i].Type != ResultType.MultiBulk) { - return null; + entries[i] = RedisStreamEntry.Null; } + else + { + // Process the Multibulk array for each entry. The entry contains the following elements: + // [0] = SimpleString (the ID of the stream entry) + // [1] = Multibulk array of the name/value pairs of the stream entry's data + var item = arr[i].GetItems(); - // Process the Multibulk array for each entry. The entry contains the following elements: - // [0] = SimpleString (the ID of the stream entry) - // [1] = Multibulk array of the name/value pairs of the stream entry's data - var item = arr[i].GetItems(); - - var id = item[0].AsRedisValue(); - var values = ParseStreamEntryValues(item[1]); + var id = item[0].AsRedisValue(); + var values = ParseStreamEntryValues(item[1]); - entries[i] = new RedisStreamEntry(id, values); + entries[i] = new RedisStreamEntry(id, values); + } } - return entries; } From 94eab14bf81e5da407cb58950e46abd849aee682 Mon Sep 17 00:00:00 2001 From: ttingen Date: Tue, 26 Jun 2018 22:09:55 -0400 Subject: [PATCH 07/15] Resolved many of the quick-hit items from the list of requested changes. - Added overload to GetStreamAcknowledgeMessage method to receive a single messageId instead of an array. - Added overload to GetStreamAddMessage to receive a single NameValueEntry instead of an array. - Corrected the constructor usage of ArgumentOutOfRangeException. - Converted to get-only properties on RedisStream and other structs. - Converted some classes to structs (StreamInfo, StreamGroupInfo, etc.) - Miscellaneous cleanup in ResultProcessor.cs & convert many for loops to Array.ConvertAll. - Renamed StreamMinValue and StreamMaxValue to ReadMinValue and ReadMaxValue. - Renamed streamFields input variable on several methods to streamPairs. - Updated XML docs (ID's to IDs). --- StackExchange.Redis.Tests/Streams.cs | 69 +++---- .../Redis/Interfaces/IDatabase.cs | 48 ++--- .../Redis/Interfaces/IDatabaseAsync.cs | 48 ++--- .../KeyspaceIsolation/DatabaseWrapper.cs | 8 +- .../Redis/KeyspaceIsolation/WrapperBase.cs | 8 +- .../StackExchange/Redis/RawResult.cs | 3 + .../StackExchange/Redis/RedisDatabase.cs | 93 ++++++--- .../StackExchange/Redis/RedisStream.cs | 18 +- .../StackExchange/Redis/RedisStreamEntry.cs | 20 +- .../StackExchange/Redis/ResultProcessor.cs | 179 ++++++++---------- .../StackExchange/Redis/StreamConstants.cs | 4 +- .../StackExchange/Redis/StreamConsumer.cs | 12 +- .../StackExchange/Redis/StreamConsumerInfo.cs | 2 +- .../StackExchange/Redis/StreamGroupInfo.cs | 2 +- .../StackExchange/Redis/StreamInfo.cs | 2 +- .../StackExchange/Redis/StreamPendingInfo.cs | 2 +- .../Redis/StreamPendingMessageInfo.cs | 2 +- 17 files changed, 264 insertions(+), 256 deletions(-) diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index fae7bf41f..4ae97cc04 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -42,7 +42,7 @@ public void StreamAddMultipleValuePairsWithAutoId() var db = conn.GetDatabase(); var messageId = db.StreamAdd(key, fields); - var entries = db.StreamRange(key, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue); + var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue); Assert.True(entries.Length == 1); Assert.Equal(messageId, entries[0].Id); @@ -89,7 +89,7 @@ public void StreamAddMultipleValuePairsWithManualId() }; var messageId = db.StreamAdd(key, id, fields); - var entries = db.StreamRange(key, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue); + var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue); Assert.Equal(id, messageId); Assert.NotNull(entries); @@ -114,7 +114,7 @@ public void StreamCreateConsumerGroup() db.StreamAdd(key, "field1", "value1"); // Create a group - var result = db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + var result = db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); Assert.True(result); } @@ -161,7 +161,7 @@ public void StreamConsumerGroupReadFromStreamBeginning() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "field2", "value2"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); var entries = db.StreamReadGroup(key, groupName, "test_consumer", "0-0"); @@ -188,12 +188,12 @@ public void StreamConsumerGroupReadFromStreamBeginningWithCount() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Start reading after id1. var entries = db.StreamReadGroup(key, groupName, "test_consumer", id1, 2); - // Ensure we only received the requested count and that the ID's match the expected values. + // Ensure we only received the requested count and that the IDs match the expected values. Assert.True(entries.Length == 2); Assert.True(id2 == entries[0].Id); Assert.True(id3 == entries[1].Id); @@ -218,7 +218,7 @@ public void StreamConsumerGroupAcknowledgeMessage() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Read all 4 messages, they will be assigned to the consumer var entries = db.StreamReadGroup(key, groupName, consumer, "0-0"); @@ -261,10 +261,10 @@ public void StreamConsumerGroupClaimMessages() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Read a single message into the first consumer. - var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.StreamMinValue, 1); + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.ReadMinValue, 1); // Read the remaining messages into the second consumer. var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); @@ -273,8 +273,8 @@ public void StreamConsumerGroupClaimMessages() // Get the pending messages for consumer2. var pendingMessages = db.StreamPendingMessageInfoGet(key, groupName, - StreamConstants.StreamMinValue, - StreamConstants.StreamMaxValue, + StreamConstants.ReadMinValue, + StreamConstants.ReadMaxValue, 10, consumer2); @@ -288,7 +288,6 @@ public void StreamConsumerGroupClaimMessages() // Now see how many messages are pending for each consumer var pendingSummary = db.StreamPendingInfoGet(key, groupName); - Assert.NotNull(pendingSummary); Assert.NotNull(pendingSummary.Consumers); Assert.True(pendingSummary.Consumers.Length == 1); Assert.Equal(4, pendingSummary.Consumers[0].PendingMessageCount); @@ -314,10 +313,10 @@ public void StreamConsumerGroupClaimMessagesReturningIds() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Read a single message into the first consumer. - var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.StreamMinValue, 1); + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.ReadMinValue, 1); // Read the remaining messages into the second consumer. var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); @@ -326,8 +325,8 @@ public void StreamConsumerGroupClaimMessagesReturningIds() // Get the pending messages for consumer2. var pendingMessages = db.StreamPendingMessageInfoGet(key, groupName, - StreamConstants.StreamMinValue, - StreamConstants.StreamMaxValue, + StreamConstants.ReadMinValue, + StreamConstants.ReadMaxValue, 10, consumer2); @@ -338,7 +337,7 @@ public void StreamConsumerGroupClaimMessagesReturningIds() 0, // Min message idle time messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray().ToStringArray()); - // We should get an array of 3 message ID's. + // We should get an array of 3 message IDs. Assert.Equal(3, messageIds.Length); Assert.Equal(id2, messageIds[0]); Assert.Equal(id3, messageIds[1]); @@ -366,17 +365,16 @@ public void StreamConsumerGroupViewPendingInfoSummary() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Read a single message into the first consumer. - var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.StreamMinValue, 1); + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.ReadMinValue, 1); // Read the remaining messages into the second consumer. var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); var pendingInfo = db.StreamPendingInfoGet(key, groupName); - Assert.NotNull(pendingInfo); Assert.Equal(4, pendingInfo.PendingMessageCount); Assert.Equal(id1, pendingInfo.LowestPendingMessageId); Assert.Equal(id4, pendingInfo.HighestPendingMessageId); @@ -409,7 +407,7 @@ public void StreamConsumerGroupViewPendingMessageInfo() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Read a single message into the first consumer. var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.UndeliveredMessages, 1); @@ -418,7 +416,7 @@ public void StreamConsumerGroupViewPendingMessageInfo() var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); // Get the pending info about the messages themselves. - var pendingMessageInfoList = db.StreamPendingMessageInfoGet(key, groupName, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue, 10); + var pendingMessageInfoList = db.StreamPendingMessageInfoGet(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 10); Assert.NotNull(pendingMessageInfoList); Assert.Equal(4, pendingMessageInfoList.Length); @@ -448,7 +446,7 @@ public void StreamConsumerGroupViewPendingMessageInfoForConsumer() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Read a single message into the first consumer. var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.UndeliveredMessages, 1); @@ -459,8 +457,8 @@ public void StreamConsumerGroupViewPendingMessageInfoForConsumer() // Get the pending info about the messages themselves. var pendingMessageInfoList = db.StreamPendingMessageInfoGet(key, groupName, - StreamConstants.StreamMinValue, - StreamConstants.StreamMaxValue, + StreamConstants.ReadMinValue, + StreamConstants.ReadMaxValue, 10, consumer2); @@ -513,8 +511,8 @@ public void StreamGroupInfoGet() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, group1, StreamConstants.StreamMinValue); - db.StreamCreateConsumerGroup(key, group2, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, group1, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, group2, StreamConstants.ReadMinValue); // Read a single message into the first consumer. var consumer1Messages = db.StreamReadGroup(key, group1, consumer1, StreamConstants.UndeliveredMessages, 1); @@ -554,7 +552,7 @@ public void StreamGroupConsumerInfoGet() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, group, StreamConstants.StreamMinValue); + db.StreamCreateConsumerGroup(key, group, StreamConstants.ReadMinValue); db.StreamReadGroup(key, group, consumer1, StreamConstants.UndeliveredMessages, 1); db.StreamReadGroup(key, group, consumer2, StreamConstants.UndeliveredMessages); @@ -589,7 +587,6 @@ public void StreamInfoGet() var streamInfo = db.StreamInfoGet(key); - Assert.NotNull(streamInfo); Assert.Equal(4, streamInfo.Length); Assert.True(streamInfo.RadixTreeKeys > 0); Assert.True(streamInfo.RadixTreeNodes > 0); @@ -660,7 +657,7 @@ public void StreamReadRange() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var entries = db.StreamRange(key, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue); + var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue); Assert.Equal(2, entries.Length); Assert.Equal(id1, entries[0].Id); @@ -682,7 +679,7 @@ public void StreamReadRangeWithCount() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var entries = db.StreamRange(key, StreamConstants.StreamMinValue, StreamConstants.StreamMaxValue, 1); + var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 1); Assert.True(entries.Length == 1); Assert.Equal(id1, entries[0].Id); @@ -703,7 +700,7 @@ public void StreamReadRangeReverse() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var entries = db.StreamRangeReverse(key, StreamConstants.StreamMaxValue, StreamConstants.StreamMinValue); + var entries = db.StreamRangeReverse(key, StreamConstants.ReadMaxValue, StreamConstants.ReadMinValue); Assert.True(entries.Length == 2); Assert.Equal(id2, entries[0].Id); @@ -725,7 +722,7 @@ public void StreamReadRangeReverseWithCount() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var entries = db.StreamRangeReverse(key, StreamConstants.StreamMaxValue, StreamConstants.StreamMinValue, 1); + var entries = db.StreamRangeReverse(key, StreamConstants.ReadMaxValue, StreamConstants.ReadMinValue, 1); Assert.True(entries.Length == 1); Assert.Equal(id2, entries[0].Id); @@ -857,9 +854,13 @@ public void StreamReadMultipleStreams() Assert.Equal(key1, streams[0].Key); Assert.True(streams[0].Entries.Length == 2); + Assert.Equal(id1, streams[0].Entries[0].Id); + Assert.Equal(id2, streams[0].Entries[1].Id); Assert.Equal(key2, streams[1].Key); Assert.True(streams[1].Entries.Length == 2); + Assert.Equal(id3, streams[1].Entries[0].Id); + Assert.Equal(id4, streams[1].Entries[1].Id); } } @@ -895,7 +896,7 @@ public void StreamReadMultipleStreamsWithCount() Assert.True(streams[0].Entries.Length == 1); Assert.True(streams[1].Entries.Length == 1); - // Check the message ID's as well. + // Check the message IDs as well. Assert.Equal(id1, streams[0].Entries[0].Id); Assert.Equal(id3, streams[1].Entries[0].Id); } diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs index aaf15e4ba..97bc4a714 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs @@ -1405,7 +1405,7 @@ IEnumerable SortedSetScan(RedisKey key, /// /// The key of the stream. /// The name of the consumer group that received the message. - /// The ID's of the messages to acknowledge. + /// The IDs of the messages to acknowledge. /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro @@ -1415,13 +1415,13 @@ IEnumerable SortedSetScan(RedisKey key, /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. /// /// The key of the stream. - /// The fields and their associated values to set in the stream entry. + /// The fields and their associated values to set in the stream entry. /// The flags to use for this operation. /// The maximum length of the stream. /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. /// The ID of the newly created message. /// https://redis.io/commands/xadd - string StreamAdd(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + string StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1455,13 +1455,13 @@ IEnumerable SortedSetScan(RedisKey key, /// /// The key of the stream. /// The ID to assign to the stream entry. This must be a unique value per entry. - /// The fields and their associated values to set in the stream entry. + /// The fields and their associated values to set in the stream entry. /// The flags to use for this operation. /// The maximum length of the stream. /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. /// The ID of the newly created message. /// https://redis.io/commands/xadd - string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). @@ -1477,15 +1477,15 @@ IEnumerable SortedSetScan(RedisKey key, RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); /// - /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the ID's for the claimed message(s). + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the IDs for the claimed message(s). /// /// The key of the stream. /// The consumer group. /// The consumer claiming the given message(s). /// The minimum message idle time to allow the reassignment of the message(s). - /// The ID(s) of the message(s) to claim for the given consumer. + /// The IDs of the messages to claim for the given consumer. /// The flags to use for this operation. - /// The message ID(s) for the messages successfully claimed by the given consumer. + /// The message IDs for the messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro string[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); @@ -1563,8 +1563,8 @@ IEnumerable SortedSetScan(RedisKey key, /// /// The key of the stream. /// The name of the consumer group. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of pending messages to return. /// The flags to use for this operation. /// An instance of for each pending message. @@ -1577,8 +1577,8 @@ IEnumerable SortedSetScan(RedisKey key, /// /// The key of the stream. /// The name of the consumer group. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of pending messages to return. /// The consumer name. /// The flags to use for this operation. @@ -1588,22 +1588,22 @@ IEnumerable SortedSetScan(RedisKey key, StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); /// - /// Read a stream using the given range of ID's. + /// Read a stream using the given range of IDs. /// /// The key of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The flags to use for this operation. /// Returns an instance of for each message returned. /// https://redis.io/commands/xrange RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None); /// - /// Read a stream in reverse order using the given range of ID's. + /// Read a stream in reverse order using the given range of IDs. /// /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The maximum number of messages to return. /// The flags to use for this operation. /// Returns an instance of for each message returned. @@ -1611,11 +1611,11 @@ IEnumerable SortedSetScan(RedisKey key, RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); /// - /// Read a stream in reverse order using the given range of ID's. + /// Read a stream in reverse order using the given range of IDs. /// /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The maximum number of messages to return. /// The flags to use for this operation. /// Returns an instance of for each message returned. @@ -1623,11 +1623,11 @@ IEnumerable SortedSetScan(RedisKey key, RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None); /// - /// Read a stream in reverse order using the given range of ID's. + /// Read a stream in reverse order using the given range of IDs. /// /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The flags to use for this operation. /// Returns an instance of for each message returned. /// https://redis.io/commands/xrevrange diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs index da908a84a..8ee67eae9 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs @@ -1315,7 +1315,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// /// The key of the stream. /// The name of the consumer group that received the message. - /// The ID's of the messages to acknowledge. + /// The IDs of the messages to acknowledge. /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro @@ -1325,13 +1325,13 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. /// /// The key of the stream. - /// The fields and their associated values to set in the stream entry. + /// The fields and their associated values to set in the stream entry. /// The flags to use for this operation. /// The maximum length of the stream. /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1365,13 +1365,13 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// /// The key of the stream. /// The ID to assign to the stream entry. This must be a unique value per entry. - /// The fields and their associated values to set in the stream entry. + /// The fields and their associated values to set in the stream entry. /// The flags to use for this operation. /// The maximum length of the stream. /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). @@ -1387,15 +1387,15 @@ Task SortedSetRangeByValueAsync(RedisKey key, Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); /// - /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the ID's for the claimed message(s). + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the IDs for the claimed message(s). /// /// The key of the stream. /// The consumer group. /// The consumer claiming the given message(s). /// The minimum message idle time to allow the reassignment of the message(s). - /// The ID(s) of the message(s) to claim for the given consumer. + /// The IDs of the messages to claim for the given consumer. /// The flags to use for this operation. - /// The message ID(s) for the messages successfully claimed by the given consumer. + /// The message IDs for the messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); @@ -1473,8 +1473,8 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// /// The key of the stream. /// The name of the consumer group. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of pending messages to return. /// The flags to use for this operation. /// An instance of for each pending message. @@ -1487,8 +1487,8 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// /// The key of the stream. /// The name of the consumer group. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of pending messages to return. /// The consumer name. /// The flags to use for this operation. @@ -1498,22 +1498,22 @@ Task SortedSetRangeByValueAsync(RedisKey key, Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); /// - /// Read a stream using the given range of ID's. + /// Read a stream using the given range of IDs. /// /// The key of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The flags to use for this operation. /// Returns an instance of for each message returned. /// https://redis.io/commands/xrange Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None); /// - /// Read a stream using the given range of ID's. + /// Read a stream using the given range of IDs. /// /// The key of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of messages to return. /// The flags to use for this operation. /// Returns an instance of for each message returned. @@ -1521,11 +1521,11 @@ Task SortedSetRangeByValueAsync(RedisKey key, Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); /// - /// Read a stream in reverse order using the given range of ID's. + /// Read a stream in reverse order using the given range of IDs. /// /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The maximum number of messages to return. /// The flags to use for this operation. /// Returns an instance of for each message returned. @@ -1533,11 +1533,11 @@ Task SortedSetRangeByValueAsync(RedisKey key, Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None); /// - /// Read a stream in reverse order using the given range of ID's. + /// Read a stream in reverse order using the given range of IDs. /// /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> + /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The flags to use for this operation. /// Returns an instance of for each message returned. /// https://redis.io/commands/xrevrange diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs index c99f368b4..801de7cad 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -597,9 +597,9 @@ public long StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messa return Inner.StreamAcknowledge(ToInner(key), groupName, messageIds, flags); } - public string StreamAdd(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public string StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - return Inner.StreamAdd(ToInner(key), streamFields, maxLength, useApproximateMaxLength, flags); + return Inner.StreamAdd(ToInner(key), streamPairs, maxLength, useApproximateMaxLength, flags); } public string StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) @@ -612,9 +612,9 @@ public string StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue strea return Inner.StreamAdd(ToInner(key), streamEntryId, streamField, streamValue, maxLength, useApproximateMaxLength, flags); } - public string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - return Inner.StreamAdd(ToInner(key), streamEntryId, streamFields, maxLength, useApproximateMaxLength, flags); + return Inner.StreamAdd(ToInner(key), streamEntryId, streamPairs, maxLength, useApproximateMaxLength, flags); } public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs index d354a7bf0..2b1e23bdd 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs @@ -576,9 +576,9 @@ public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, str return Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageIds, flags); } - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - return Inner.StreamAddAsync(ToInner(key), streamFields, maxLength, useApproximateMaxLength, flags); + return Inner.StreamAddAsync(ToInner(key), streamPairs, maxLength, useApproximateMaxLength, flags); } public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) @@ -591,9 +591,9 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, Redis return Inner.StreamAddAsync(ToInner(key), streamEntryId, streamField, streamValue, maxLength, useApproximateMaxLength, flags); } - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - return Inner.StreamAddAsync(ToInner(key), streamEntryId, streamFields, maxLength, useApproximateMaxLength, flags); + return Inner.StreamAddAsync(ToInner(key), streamEntryId, streamPairs, maxLength, useApproximateMaxLength, flags); } public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) diff --git a/StackExchange.Redis/StackExchange/Redis/RawResult.cs b/StackExchange.Redis/StackExchange/Redis/RawResult.cs index f4c268e7d..98a9e19ca 100644 --- a/StackExchange.Redis/StackExchange/Redis/RawResult.cs +++ b/StackExchange.Redis/StackExchange/Redis/RawResult.cs @@ -7,6 +7,9 @@ internal struct RawResult { public static readonly RawResult EmptyArray = new RawResult(new RawResult[0]); public static readonly RawResult Nil = new RawResult(); + + public static RawResult CreateMultiBulk(params RawResult[] results) => new RawResult(results); + private static readonly byte[] emptyBlob = new byte[0]; private readonly int offset, count; private readonly Array arr; diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index 54b5841fd..65f08abf4 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -1603,14 +1603,14 @@ public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, str return ExecuteAsync(msg, ResultProcessor.Int64); } - public string StreamAdd(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public string StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, StreamConstants.AutoGeneratedId, maxLength, useApproximateMaxLength, flags, - streamFields); + streamPairs); return ExecuteSync(msg, ResultProcessor.String); } @@ -1638,34 +1638,32 @@ public string StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue strea return ExecuteSync(msg, ResultProcessor.String); } - public string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, streamEntryId, maxLength, useApproximateMaxLength, flags, - streamFields); + streamPairs); return ExecuteSync(msg, ResultProcessor.String); } - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, StreamConstants.AutoGeneratedId, maxLength, useApproximateMaxLength, flags, - streamFields); + streamPairs); return ExecuteAsync(msg, ResultProcessor.String); } public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - // @@ Think of better names for the input variables. - var msg = GetStreamAddMessage(key, StreamConstants.AutoGeneratedId, maxLength, @@ -1688,14 +1686,14 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, Redis return ExecuteAsync(msg, ResultProcessor.String); } - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamFields, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, streamEntryId, maxLength, useApproximateMaxLength, flags, - streamFields); + streamPairs); return ExecuteAsync(msg, ResultProcessor.String); } @@ -2805,7 +2803,19 @@ private Message GetSortedSetRemoveRangeByScoreMessage(RedisKey key, double start GetRange(start, exclude, true), GetRange(stop, exclude, false)); } - private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, CommandFlags flags, params string[] messageIds) + private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, CommandFlags flags, string messageId) + { + var arr = new RedisValue[3] + { + key.AsRedisValue(), + groupName, + messageId + }; + + return Message.Create(Database, flags, RedisCommand.XACK, arr); + } + + private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, CommandFlags flags, string[] messageIds) { var arr = new RedisValue[messageIds.Length + 2]; @@ -2822,25 +2832,60 @@ private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, return Message.Create(Database, flags, RedisCommand.XACK, arr); } - private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags, params NameValueEntry[] streamFields) + private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags, NameValueEntry streamPair) + { + // Calculate the correct number of arguments: + // 3 array elements for Entry ID & NameValueEntry.Name & NameValueEntry.Value. + // 2 elements if using MAXLEN (keyword & value), otherwise 0. + // 1 element if using Approximate Length (~), otherwise 0. + var totalLength = 3 + (maxLength.HasValue ? 2 : 0) + + (maxLength.HasValue && useApproximateMaxLength ? 1 : 0); + + var values = new RedisValue[totalLength]; + var offset = 0; + + values[offset++] = entryId; + + if (maxLength.HasValue) + { + values[offset++] = StreamConstants.MaxLen; + + if (useApproximateMaxLength) + { + values[offset++] = StreamConstants.ApproximateMaxLen; + values[offset++] = maxLength.Value; + } + else + { + values[offset++] = maxLength.Value; + } + } + + values[offset++] = streamPair.Name; + values[offset++] = streamPair.Value; + + return Message.Create(Database, flags, RedisCommand.XADD, key, values); + } + + private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags, NameValueEntry[] streamPairs) { // See https://redis.io/commands/xadd. - if (streamFields == null) throw new ArgumentNullException(nameof(streamFields)); - if (streamFields.Length == 0) throw new ArgumentOutOfRangeException("streamFields must contain at least one field."); + if (streamPairs == null) throw new ArgumentNullException(nameof(streamPairs)); + if (streamPairs.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPairs), "streamPairs must contain at least one field."); if (maxLength.HasValue && maxLength <= 0) { - throw new ArgumentOutOfRangeException("maxLength must be greater than 0."); + throw new ArgumentOutOfRangeException(nameof(maxLength), "maxLength must be greater than 0."); } var includeMaxLen = maxLength.HasValue ? 2 : 0; var includeApproxLen = maxLength.HasValue && useApproximateMaxLength ? 1 : 0; - var totalLength = (streamFields.Length * 2) // Room for the name/value pairs - + 1 // The stream entry ID - + includeMaxLen // 2 or 0 (MAXLEN keyword & the count) - + includeApproxLen; // 1 or 0 + var totalLength = (streamPairs.Length * 2) // Room for the name/value pairs + + 1 // The stream entry ID + + includeMaxLen // 2 or 0 (MAXLEN keyword & the count) + + includeApproxLen; // 1 or 0 var values = new RedisValue[totalLength]; @@ -2860,10 +2905,10 @@ private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLe values[offset++] = maxLength.Value; } - for (var i = 0; i < streamFields.Length; i++) + for (var i = 0; i < streamPairs.Length; i++) { - values[offset++] = streamFields[i].Name; - values[offset++] = streamFields[i].Value; + values[offset++] = streamPairs[i].Name; + values[offset++] = streamPairs[i].Value; } return Message.Create(Database, flags, RedisCommand.XADD, key, values); @@ -2915,7 +2960,7 @@ private Message GetStreamPendingMessagesMessage(RedisKey key, RedisValue groupNa if (count <= 0) { - throw new ArgumentOutOfRangeException("count must be greater than 0."); + throw new ArgumentOutOfRangeException(nameof(count), "count must be greater than 0."); } var values = new RedisValue[consumerName == RedisValue.Null ? 5 : 6]; @@ -2942,7 +2987,7 @@ private Message GetStreamReadGroupMessage(RedisKey key, RedisValue groupName, Re // Example: > XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream > if (count.HasValue && count <= 0) { - throw new ArgumentOutOfRangeException("count must be greater than 0."); + throw new ArgumentOutOfRangeException(nameof(count), "count must be greater than 0."); } var totalValueCount = 6 + (count.HasValue ? 2 : 0); diff --git a/StackExchange.Redis/StackExchange/Redis/RedisStream.cs b/StackExchange.Redis/StackExchange/Redis/RedisStream.cs index f0ce1ffbd..0383f4ee1 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisStream.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisStream.cs @@ -5,28 +5,20 @@ /// public struct RedisStream { - internal readonly RedisKey key; - internal readonly RedisStreamEntry[] entries; - - /// - /// Initializes a instance. - /// - /// The key for the stream. - /// An arry of entries contained within the stream. - public RedisStream(RedisKey key, RedisStreamEntry[] entries) + internal RedisStream(RedisKey key, RedisStreamEntry[] entries) { - this.key = key; - this.entries = entries; + Key = key; + Entries = entries; } /// /// The key for the stream. /// - public RedisKey Key => key; + public RedisKey Key { get; } /// /// An arry of entries contained within the stream. /// - public RedisStreamEntry[] Entries => entries; + public RedisStreamEntry[] Entries { get; } } } diff --git a/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs b/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs index f3c59a64f..592a9508d 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisStreamEntry.cs @@ -5,18 +5,10 @@ /// public struct RedisStreamEntry { - internal readonly RedisValue id; - internal readonly NameValueEntry[] values; - - /// - /// Initializes a instance. - /// - /// The ID assigned to the message. - /// The values contained within the message. - public RedisStreamEntry(RedisValue id, NameValueEntry[] values) + internal RedisStreamEntry(RedisValue id, NameValueEntry[] values) { - this.id = id; - this.values = values; + Id = id; + Values = values; } /// @@ -27,16 +19,16 @@ public RedisStreamEntry(RedisValue id, NameValueEntry[] values) /// /// The ID assigned to the message. /// - public RedisValue Id => id; + public RedisValue Id { get; } /// /// The values contained within the message. /// - public NameValueEntry[] Values => values; + public NameValueEntry[] Values { get; } /// /// Indicates that the Redis Stream Entry is null. /// - public bool IsNull => id == RedisValue.Null && values == null; + public bool IsNull => Id == RedisValue.Null && Values == null; } } diff --git a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs b/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs index f14a35a2c..82c36145c 100644 --- a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs +++ b/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs @@ -1348,28 +1348,35 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes return false; } + RedisStreamEntry[] entries = null; + if (skipStreamName) { - // Possibly validate that the command was XREAD? XREAD is the only - // command that should use this option. - // Skip the first element in the array (i.e., the stream name). // See https://redis.io/commands/xread. // > XREAD COUNT 2 STREAMS mystream 0 // 1) 1) "mystream" <== Skip the stream name - // 2) 1) 1) 1519073278252 - 0 <== Index 1 contains the array of messages + // 2) 1) 1) 1519073278252 - 0 <== Index 1 contains the array of stream entries // 2) 1) "foo" // 2) "value_1" // 2) 1) 1519073279157 - 0 // 2) 1) "foo" // 2) "value_2" - result = result.GetItems()[0] // Initial result array, then the first stream. - .GetItems()[1]; // Skip the stream name and return the array of stream entries. - } + // Retrieve the initial array. For XREAD of a single stream it will + // be an array of only 1 element in the response. + var readResult = result.GetItems(); - var entries = ParseRedisStreamEntries(result); + // Within that single element, GetItems will return an array of + // 2 elements: the stream name and the stream entries. + // Skip the stream name (index 0) and only process the stream entries (index 1). + entries = ParseRedisStreamEntries(readResult[0].GetItems()[1]); + } + else + { + entries = ParseRedisStreamEntries(result); + } SetResult(message, entries); return true; @@ -1379,7 +1386,6 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes internal sealed class MultiStreamProcessor : StreamProcessorBase { /* - The result is similar to the XRANGE result (see SingleStreamProcessor) with the addition of the stream name as the first element of top level Multibulk array. @@ -1427,25 +1433,17 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes var arr = result.GetItems(); - var streams = new RedisStream[arr.Length]; - - for (var i = 0; i < arr.Length; i++) + var streams = Array.ConvertAll(arr, item => { - if (arr[i].Type != ResultType.MultiBulk) - { - return false; - } - - var stream = arr[i].GetItems(); + var details = item.GetItems(); - // stream[0] = Name of the Stream - // stream[1] = Multibulk Array of Stream Entries + // details[0] = Name of the Stream + // details[1] = Multibulk Array of Stream Entries - var streamName = stream[0].AsRedisKey(); - var streamEntries = ParseRedisStreamEntries(stream[1]); - - streams[i] = new RedisStream(streamName, streamEntries); - } + return new RedisStream( + key: details[0].AsRedisKey(), + entries: ParseRedisStreamEntries(details[1])); + }); SetResult(message, streams); return true; @@ -1526,12 +1524,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes } var arr = result.GetItems(); - var parsedItems = new T[arr.Length]; - - for (var i = 0; i < arr.Length; i++) - { - parsedItems[i] = ParseItem(arr[i]); - } + var parsedItems = Array.ConvertAll(arr, item => ParseItem(item)); SetResult(message, parsedItems); return true; @@ -1574,29 +1567,23 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes return false; } - // The first four items are interleaved name/value pairs. - // Skip the field name and just get the value. - - var length = (int)arr[1].AsRedisValue(); - var treeKeyCount = (int)arr[3].AsRedisValue(); - var treeNodeCount = (int)arr[5].AsRedisValue(); - var groupCount = (int)arr[7].AsRedisValue(); - // Note: Even if there is only 1 message in the stream, this command returns // the single entry as the first-entry and last-entry in the response. - var entries = ParseRedisStreamEntries(new RawResult(new RawResult[] - { - arr[9], - arr[11] - })); + // The first 8 items are interleaved name/value pairs. + // Items 9-12 represent the first and last entry in the stream. The values will + // be nil (stored in index 9 & 11) if the stream length is 0. + + var entries = ParseRedisStreamEntries(RawResult.CreateMultiBulk(arr[9], arr[11])); + + var streamInfo = new StreamInfo( + (int)arr[1].AsRedisValue(), // Stream Length + (int)arr[3].AsRedisValue(), // Radix tree keys + (int)arr[5].AsRedisValue(), // Radix tree nodes + (int)arr[7].AsRedisValue(), // Consumer Group Count + entries[0], // First stream entry + entries[1]); // Last stream entry - var streamInfo = new StreamInfo(length, - treeKeyCount, - treeNodeCount, - groupCount, - entries[0], - entries[1]); SetResult(message, streamInfo); return true; @@ -1614,6 +1601,8 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes // 3) 1526569506935 - 0 // 4) 1) 1) "Bob" // 2) "2" + // 5) 1) 1) "Joe" + // 2) "8" if (result.Type != ResultType.MultiBulk) { @@ -1632,20 +1621,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes var highestPendingMessageId = arr[2].AsRedisValue(); // Get the list of Consumers - arr = arr[3].GetItems(); - - var consumers = new StreamConsumer[arr.Length]; - - for (var i = 0; i < arr.Length; i++) + var consumers = Array.ConvertAll(arr[3].GetItems(), item => { - var details = arr[i].GetItems(); + var details = item.GetItems(); - consumers[i] = new StreamConsumer - { - Name = details[0].AsRedisValue(), - PendingMessageCount = details[1].AsRedisValue() - }; - } + return new StreamConsumer( + name: details[0].AsRedisValue(), + pendingMessageCount: details[1].AsRedisValue()); + }); var pendingInfo = new StreamPendingInfo(pendingMessageCount, lowestPendingMessageId, @@ -1668,24 +1651,18 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes var arr = result.GetItems(); - var messages = new StreamPendingMessageInfo[arr.Length]; - - for (var i = 0; i < arr.Length; i++) + var messageInfoArray = Array.ConvertAll(arr, item => { - var details = arr[i].GetItems(); + var details = item.GetItems(); - var messageId = details[0].AsRedisValue(); - var consumerName = details[1].AsRedisValue(); - var idleTimeInMs = details[2].AsRedisValue(); - var deliveryCount = details[3].AsRedisValue(); + return new StreamPendingMessageInfo( + messageId: details[0].AsRedisValue(), + consumerName: details[1].AsRedisValue(), + idleTimeInMs: details[2].AsRedisValue(), + deliveryCount: details[3].AsRedisValue()); + }); - messages[i] = new StreamPendingMessageInfo(messageId, - consumerName, - idleTimeInMs, - deliveryCount); - } - - SetResult(message, messages); + SetResult(message, messageInfoArray); return true; } } @@ -1703,28 +1680,22 @@ protected RedisStreamEntry[] ParseRedisStreamEntries(RawResult result) var arr = result.GetItems(); - var entries = new RedisStreamEntry[arr.Length]; - - for (var i = 0; i < arr.Length; i++) + return Array.ConvertAll(arr, item => { - if (arr[i].IsNull || arr[i].Type != ResultType.MultiBulk) + if (item.IsNull || item.Type != ResultType.MultiBulk) { - entries[i] = RedisStreamEntry.Null; + return RedisStreamEntry.Null; } - else - { - // Process the Multibulk array for each entry. The entry contains the following elements: - // [0] = SimpleString (the ID of the stream entry) - // [1] = Multibulk array of the name/value pairs of the stream entry's data - var item = arr[i].GetItems(); - var id = item[0].AsRedisValue(); - var values = ParseStreamEntryValues(item[1]); + // Process the Multibulk array for each entry. The entry contains the following elements: + // [0] = SimpleString (the ID of the stream entry) + // [1] = Multibulk array of the name/value pairs of the stream entry's data + var entryDetails = item.GetItems(); - entries[i] = new RedisStreamEntry(id, values); - } - } - return entries; + return new RedisStreamEntry( + id: entryDetails[0].AsRedisValue(), + values: ParseStreamEntryValues(entryDetails[1])); + }); } protected NameValueEntry[] ParseStreamEntryValues(RawResult result) @@ -1757,23 +1728,21 @@ protected NameValueEntry[] ParseStreamEntryValues(RawResult result) return null; } - NameValueEntry[] pairs; - + // Calculate how many name/value pairs are in the stream entry. int count = arr.Length / 2; if (count == 0) { - pairs = new NameValueEntry[0]; + return new NameValueEntry[0]; } - else + + var pairs = new NameValueEntry[count]; + int offset = 0; + + for (int i = 0; i < pairs.Length; i++) { - pairs = new NameValueEntry[count]; - int offset = 0; - for (int i = 0; i < pairs.Length; i++) - { - pairs[i] = new NameValueEntry(arr[offset++].AsRedisValue(), - arr[offset++].AsRedisValue()); - } + pairs[i] = new NameValueEntry(arr[offset++].AsRedisValue(), + arr[offset++].AsRedisValue()); } return pairs; diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs b/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs index 293a32132..b5a00ea08 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs @@ -24,12 +24,12 @@ public static class StreamConstants /// /// The "-" value used in the XRANGE, XREAD, and XREADGROUP commands. Indicates the minimum message ID from the stream. /// - public static readonly RedisValue StreamMinValue = "-"; + public static readonly RedisValue ReadMinValue = "-"; /// /// The "+" value used in the XRANGE, XREAD, and XREADGROUP commands. Indicates the maximum message ID from the stream. /// - public static readonly RedisValue StreamMaxValue = "+"; + public static readonly RedisValue ReadMaxValue = "+"; /// /// The ">" value used in the XREADGROUP command. Use this to read messages that have not been delivered to a consumer group. diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs b/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs index 006b2666f..50530450b 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs @@ -4,16 +4,22 @@ namespace StackExchange.Redis /// /// Describes a consumer off a Redis Stream. /// - public class StreamConsumer + public struct StreamConsumer { + internal StreamConsumer(RedisValue name, RedisValue pendingMessageCount) + { + Name = name; + PendingMessageCount = pendingMessageCount; + } + /// /// The name of the consumer. /// - public RedisValue Name { get; set; } + public RedisValue Name { get; } /// /// The number of messages that have been delivered by not yet acknowledged by the consumer. /// - public RedisValue PendingMessageCount { get; set; } + public RedisValue PendingMessageCount { get; } } } diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs index abfee2309..27328abde 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs @@ -4,7 +4,7 @@ namespace StackExchange.Redis /// /// Describes a consumer within a consumer group, retrieved using the XINFO CONSUMERS command. /// - public class StreamConsumerInfo + public struct StreamConsumerInfo { internal StreamConsumerInfo(string name, int pendingMessageCount, long idleTimeInMilliseconds) { diff --git a/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs index aea71cc33..cafc3484f 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs @@ -4,7 +4,7 @@ namespace StackExchange.Redis /// /// Describes a consumer group retrieved using the XINFO GROUPS command. /// - public class StreamGroupInfo + public struct StreamGroupInfo { internal StreamGroupInfo(string name, int consumerCount, int pendingMessageCount) { diff --git a/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs index 21d8c6c11..497572853 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs @@ -4,7 +4,7 @@ namespace StackExchange.Redis /// /// Describes stream information retrieved using the XINFO STREAM command. /// - public class StreamInfo + public struct StreamInfo { internal StreamInfo(int length, int radixTreeKeys, diff --git a/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs index 779affea0..eff934606 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs @@ -4,7 +4,7 @@ namespace StackExchange.Redis /// /// Describes basic information about pending messages for a consumer group. /// - public class StreamPendingInfo + public struct StreamPendingInfo { internal StreamPendingInfo(RedisValue pendingMessageCount, RedisValue lowestId, diff --git a/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs index 50b68f489..855db5b6f 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs @@ -5,7 +5,7 @@ namespace StackExchange.Redis /// Describes properties of a pending message. A pending message is one that has /// been received by a consumer but has not yet been acknowledged. /// - public class StreamPendingMessageInfo + public struct StreamPendingMessageInfo { internal StreamPendingMessageInfo(RedisValue messageId, RedisValue consumerName, From 9c5e3410a545faca0560dc3401a8c27ea7c8f793 Mon Sep 17 00:00:00 2001 From: ttingen Date: Fri, 29 Jun 2018 17:02:09 -0400 Subject: [PATCH 08/15] Completed requested changes. - Reverted changes to the Tests csproj file. - Updated the type for messageId from string to RedisValue. - Collapsed several overloads. - Updated the method order in several places to follow the sync/async pattern. - Added more tests. - Miscellaneous cleanup. Signed-off-by: ttingen --- .../DatabaseWrapperTests.cs | 71 +- .../StackExchange.Redis.Tests.csproj | 4 - StackExchange.Redis.Tests/Streams.cs | 482 ++++++++++--- StackExchange.Redis.Tests/WrapperBaseTests.cs | 73 +- .../Redis/Interfaces/IDatabase.cs | 106 +-- .../Redis/Interfaces/IDatabaseAsync.cs | 106 +-- .../KeyspaceIsolation/DatabaseWrapper.cs | 58 +- .../Redis/KeyspaceIsolation/WrapperBase.cs | 58 +- .../StackExchange/Redis/RedisDatabase.cs | 674 +++++++----------- .../StackExchange/Redis/ResultProcessor.cs | 80 +-- .../StackExchange/Redis/StreamConstants.cs | 2 - .../StackExchange/Redis/StreamConsumer.cs | 4 +- .../StackExchange/Redis/StreamPendingInfo.cs | 4 +- .../Redis/StreamPendingMessageInfo.cs | 8 +- 14 files changed, 778 insertions(+), 952 deletions(-) diff --git a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs index acec39747..d13ae01dd 100644 --- a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs +++ b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs @@ -792,7 +792,7 @@ public void StreamAcknowledge_1() [Fact] public void StreamAcknowledge_2() { - var messageIds = new string[] { "0-0", "0-1", "0-2" }; + var messageIds = new RedisValue[] { "0-0", "0-1", "0-2" }; wrapper.StreamAcknowledge("key", "group", messageIds, CommandFlags.HighPriority); mock.Verify(_ => _.StreamAcknowledge("prefix:key", "group", messageIds, CommandFlags.HighPriority)); } @@ -830,7 +830,7 @@ public void StreamAdd_4() [Fact] public void StreamClaimMessages() { - var messageIds = new string[0]; + var messageIds = new RedisValue[0]; wrapper.StreamClaimMessages("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); mock.Verify(_ => _.StreamClaimMessages("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); } @@ -838,7 +838,7 @@ public void StreamClaimMessages() [Fact] public void StreamClaimMessagesReturningIds() { - var messageIds = new string[0]; + var messageIds = new RedisValue[0]; wrapper.StreamClaimMessagesReturningIds("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); mock.Verify(_ => _.StreamClaimMessagesReturningIds("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); } @@ -881,7 +881,7 @@ public void StreamLength() [Fact] public void StreamMessagesDelete() { - var messageIds = new string[0] { }; + var messageIds = new RedisValue[0] { }; wrapper.StreamMessagesDelete("key", messageIds, CommandFlags.HighPriority); mock.Verify(_ => _.StreamMessagesDelete("prefix:key", messageIds, CommandFlags.HighPriority)); } @@ -894,75 +894,32 @@ public void StreamPendingInfoGet() } [Fact] - public void StreamPendingMessageInfoGet_1() + public void StreamPendingMessageInfoGet() { - wrapper.StreamPendingMessageInfoGet("key", "group", "-", "+", 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingMessageInfoGet("prefix:key", "group", "-", "+", 10, CommandFlags.HighPriority)); + wrapper.StreamPendingMessageInfoGet("key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessageInfoGet("prefix:key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority)); } [Fact] - public void StreamPendingMessageInfoGet_2() + public void StreamRange() { - wrapper.StreamPendingMessageInfoGet("key", "group", "-", "+", 10, "consumer", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingMessageInfoGet("prefix:key", "group", "-", "+", 10, "consumer", CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRange_1() - { - wrapper.StreamRange("key", "-", "+", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRange("prefix:key", "-", "+", CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRange_2() - { - wrapper.StreamRange("key", "-", "+", 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRange("prefix:key", "-", "+", 10, CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRangeReverse_1() - { - wrapper.StreamRangeReverse("key", "+", "-", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRangeReverse("prefix:key", "+", "-", CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRangeReverse_2() - { - wrapper.StreamRangeReverse("key", "+", "-", 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRangeReverse("prefix:key", "+", "-", 10, CommandFlags.HighPriority)); + wrapper.StreamRange("key", "-", "+", null, Order.Ascending, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRange("prefix:key", "-", "+",null, Order.Ascending, CommandFlags.HighPriority)); } [Fact] public void StreamRead_1() { var keysAndIds = new KeyValuePair[0] { }; - wrapper.StreamRead(keysAndIds, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRead(keysAndIds, CommandFlags.HighPriority)); + wrapper.StreamRead(keysAndIds, null, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRead(keysAndIds, null, CommandFlags.HighPriority)); } [Fact] public void StreamRead_2() { - wrapper.StreamRead("key", "0-0", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRead("prefix:key", "0-0", CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRead_3() - { - var keysAndIds = new KeyValuePair[0] { }; - wrapper.StreamRead(keysAndIds, 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRead(keysAndIds, 10, CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRead_4() - { - wrapper.StreamRead("key", "0-0", 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRead("prefix:key", "0-0", 10, CommandFlags.HighPriority)); + wrapper.StreamRead("key", "0-0", null, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRead("prefix:key", "0-0", null, CommandFlags.HighPriority)); } [Fact] diff --git a/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj b/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj index 2e8af78b5..b11e297df 100644 --- a/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj +++ b/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj @@ -28,8 +28,4 @@ - - - - diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index 4ae97cc04..02402f7fa 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -20,7 +20,7 @@ public void StreamAddSinglePairWithAutoId() var db = conn.GetDatabase(); var messageId = db.StreamAdd(GetUniqueKey("auto_id"), "field1", "value1"); - Assert.True(messageId != null && messageId.Length > 0); + Assert.True(messageId != RedisValue.Null && ((string)messageId).Length > 0); } } @@ -229,7 +229,7 @@ public void StreamConsumerGroupAcknowledgeMessage() var oneAck = db.StreamAcknowledge(key, groupName, id1); // Multiple message Id overload. - var twoAck = db.StreamAcknowledge(key, groupName, new string[] { id3, id4 }); + var twoAck = db.StreamAcknowledge(key, groupName, new RedisValue[] { id3, id4 }); // Read the group again, it should only return the unacknowledged message. var notAcknowledged = db.StreamReadGroup(key, groupName, consumer, "0-0"); @@ -283,7 +283,7 @@ public void StreamConsumerGroupClaimMessages() groupName, consumer1, 0, // Min message idle time - messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray().ToStringArray()); + messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray()); // Now see how many messages are pending for each consumer var pendingSummary = db.StreamPendingInfoGet(key, groupName); @@ -335,7 +335,7 @@ public void StreamConsumerGroupClaimMessagesReturningIds() groupName, consumer1, 0, // Min message idle time - messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray().ToStringArray()); + messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray()); // We should get an array of 3 message IDs. Assert.Equal(3, messageIds.Length); @@ -346,6 +346,60 @@ public void StreamConsumerGroupClaimMessagesReturningIds() } + [Fact] + public void StreamConsumerGroupViewPendingInfoNoConsumers() + { + var key = GetUniqueKey("group_pending_info_no_consumers"); + var groupName = "test_group"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + + var pendingInfo = db.StreamPendingInfoGet(key, groupName); + + Assert.Equal(0, pendingInfo.PendingMessageCount); + Assert.True(pendingInfo.LowestPendingMessageId == RedisValue.Null); + Assert.True(pendingInfo.HighestPendingMessageId == RedisValue.Null); + Assert.NotNull(pendingInfo.Consumers); + Assert.True(pendingInfo.Consumers.Length == 0); + } + } + + [Fact] + public void StreamConsumerGroupViewPendingInfoWhenNothingPending() + { + var key = GetUniqueKey("group_pending_info_nothing_pending"); + var groupName = "test_group"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + + var pendingMessages = db.StreamPendingMessageInfoGet(key, + groupName, + StreamConstants.ReadMinValue, + StreamConstants.ReadMaxValue, + 10, + consumerName: RedisValue.Null); + + Assert.NotNull(pendingMessages); + Assert.True(pendingMessages.Length == 0); + } + } + [Fact] public void StreamConsumerGroupViewPendingInfoSummary() { @@ -416,7 +470,7 @@ public void StreamConsumerGroupViewPendingMessageInfo() var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); // Get the pending info about the messages themselves. - var pendingMessageInfoList = db.StreamPendingMessageInfoGet(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 10); + var pendingMessageInfoList = db.StreamPendingMessageInfoGet(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 10, RedisValue.Null); Assert.NotNull(pendingMessageInfoList); Assert.Equal(4, pendingMessageInfoList.Length); @@ -483,7 +537,7 @@ public void StreamDeleteMessage() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - var deletedCount = db.StreamMessagesDelete(key, new string[1] { id3 }, CommandFlags.None); + var deletedCount = db.StreamMessagesDelete(key, new RedisValue[1] { id3 }, CommandFlags.None); var messages = db.StreamRange(key, "-", "+"); Assert.Equal(1, deletedCount); @@ -491,6 +545,30 @@ public void StreamDeleteMessage() } } + [Fact] + public void StreamDeleteMessages() + { + var key = GetUniqueKey("delete_msgs"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + var deletedCount = db.StreamMessagesDelete(key, new RedisValue[2] { id2, id3 }, CommandFlags.None); + var messages = db.StreamRange(key, "-", "+"); + + Assert.Equal(2, deletedCount); + Assert.Equal(2, messages.Length); + } + } + [Fact] public void StreamGroupInfoGet() { @@ -611,7 +689,7 @@ public void StreamInfoGetWithEmptyStream() // and last-entry messages should be null. var id = db.StreamAdd(key, "field1", "value1"); - db.StreamMessagesDelete(key, new string[1] { id }); + db.StreamMessagesDelete(key, new RedisValue[1] { id }); Assert.Equal(0, db.StreamLength(key)); @@ -623,30 +701,9 @@ public void StreamInfoGetWithEmptyStream() } [Fact] - public void StreamVerifyLength() - { - var key = GetUniqueKey("len"); - - using (var conn = Create()) - { - Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); - - var db = conn.GetDatabase(); - - // Add a couple items and check length. - db.StreamAdd(key, "field1", "value1"); - db.StreamAdd(key, "fiedl2", "value2"); - - var len = db.StreamLength(key); - - Assert.Equal(2, len); - } - } - - [Fact] - public void StreamReadRange() + public void StreamRead() { - var key = GetUniqueKey("range"); + var key = GetUniqueKey("read"); using (var conn = Create()) { @@ -656,19 +713,22 @@ public void StreamReadRange() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); - var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue); + // Read the entire stream from the beginning. + var entries = db.StreamRead(key, "0-0"); - Assert.Equal(2, entries.Length); + Assert.True(entries.Length == 3); Assert.Equal(id1, entries[0].Id); Assert.Equal(id2, entries[1].Id); + Assert.Equal(id3, entries[2].Id); } } [Fact] - public void StreamReadRangeWithCount() + public void StreamReadEmptyStream() { - var key = GetUniqueKey("range_count"); + var key = GetUniqueKey("read_empty_stream"); using (var conn = Create()) { @@ -676,20 +736,26 @@ public void StreamReadRangeWithCount() var db = conn.GetDatabase(); + // Write to a stream to create the key. var id1 = db.StreamAdd(key, "field1", "value1"); - var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 1); + // Delete the key to empty the stream. + db.StreamMessagesDelete(key, new RedisValue[1] { id1 }); + var len = db.StreamLength(key); - Assert.True(entries.Length == 1); - Assert.Equal(id1, entries[0].Id); + // Read the entire stream from the beginning. + var entries = db.StreamRead(key, "0-0"); + + Assert.True(entries.Length == 0); + Assert.Equal(0, len); } } [Fact] - public void StreamReadRangeReverse() + public void StreamReadEmptyStreams() { - var key = GetUniqueKey("rangerev"); + var key1 = GetUniqueKey("read_empty_stream_1"); + var key2 = GetUniqueKey("read_empty_stream_2"); using (var conn = Create()) { @@ -697,130 +763,88 @@ public void StreamReadRangeReverse() var db = conn.GetDatabase(); - var id1 = db.StreamAdd(key, "field1", "value1"); - var id2 = db.StreamAdd(key, "fiedl2", "value2"); - - var entries = db.StreamRangeReverse(key, StreamConstants.ReadMaxValue, StreamConstants.ReadMinValue); - - Assert.True(entries.Length == 2); - Assert.Equal(id2, entries[0].Id); - Assert.Equal(id1, entries[1].Id); - } - } - - [Fact] - public void StreamReadRangeReverseWithCount() - { - var key = GetUniqueKey("rangerev_count"); + // Write to a stream to create the key. + var id1 = db.StreamAdd(key1, "field1", "value1"); + var id2 = db.StreamAdd(key2, "field2", "value2"); - using (var conn = Create()) - { - Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + // Delete the key to empty the stream. + db.StreamMessagesDelete(key1, new RedisValue[1] { id1 }); + db.StreamMessagesDelete(key2, new RedisValue[1] { id2 }); - var db = conn.GetDatabase(); + var len1 = db.StreamLength(key1); + var len2 = db.StreamLength(key2); - var id1 = db.StreamAdd(key, "field1", "value1"); - var id2 = db.StreamAdd(key, "fiedl2", "value2"); + // Read the entire stream from the beginning. + var entries1 = db.StreamRead(key1, "0-0"); + var entries2 = db.StreamRead(key2, "0-0"); - var entries = db.StreamRangeReverse(key, StreamConstants.ReadMaxValue, StreamConstants.ReadMinValue, 1); + Assert.True(entries1.Length == 0); + Assert.True(entries2.Length == 0); - Assert.True(entries.Length == 1); - Assert.Equal(id2, entries[0].Id); + Assert.Equal(0, len1); + Assert.Equal(0, len2); } } [Fact] - public void StreamRead() + public void StreamReadExpectedExceptionInvalidCountMultipleStream() { - var key = GetUniqueKey("read"); + var key = GetUniqueKey("read_exception_invalid_count_multiple"); using (var conn = Create()) { Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); - var db = conn.GetDatabase(); - - var id1 = db.StreamAdd(key, "field1", "value1"); - var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var id3 = db.StreamAdd(key, "field3", "value3"); + var streamPairs = new List> + { + new KeyValuePair("key1", "0-0"), + new KeyValuePair("key2", "0-0") + }; - // Read the entire stream from the beginning. - var entries = db.StreamRead(key, "0-0"); - Assert.True(entries.Length == 3); - Assert.Equal(id1, entries[0].Id); - Assert.Equal(id2, entries[1].Id); - Assert.Equal(id3, entries[2].Id); + var db = conn.GetDatabase(); + Assert.Throws(() => db.StreamRead(streamPairs, 0)); } } [Fact] - public void StreamReadWithAfterIdAndCount_1() + public void StreamReadExpectedExceptionInvalidCountSingleStream() { - var key = GetUniqueKey("read"); + var key = GetUniqueKey("read_exception_invalid_count_single"); using (var conn = Create()) { Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); var db = conn.GetDatabase(); - - var id1 = db.StreamAdd(key, "field1", "value1"); - var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var id3 = db.StreamAdd(key, "field3", "value3"); - - // Only read a single item from the stream. - var entries = db.StreamRead(key, id1, 1); - - Assert.True(entries.Length == 1); - Assert.Equal(id2, entries[0].Id); + Assert.Throws(() => db.StreamRead(key, "0-0", 0)); } } [Fact] - public void StreamReadWithAfterIdAndCount_2() + public void StreamReadExpectedExceptionNullStreamList() { - var key = GetUniqueKey("read"); - using (var conn = Create()) { Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); var db = conn.GetDatabase(); - - var id1 = db.StreamAdd(key, "field1", "value1"); - var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var id3 = db.StreamAdd(key, "field3", "value3"); - var id4 = db.StreamAdd(key, "field4", "value4"); - - // Read multiple items from the stream. - var entries = db.StreamRead(key, id1, 2); - - Assert.True(entries.Length == 2); - Assert.Equal(id2, entries[0].Id); - Assert.Equal(id3, entries[1].Id); + Assert.Throws(() => db.StreamRead(null)); } } [Fact] - public void StreamReadPastEndOfStream() + public void StreamReadExpectedExceptionEmptyStreamList() { - var key = GetUniqueKey("read_empty"); - using (var conn = Create()) { Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); var db = conn.GetDatabase(); - var id1 = db.StreamAdd(key, "field1", "value1"); - var id2 = db.StreamAdd(key, "fiedl2", "value2"); - - // Read after the final ID in the stream, we expect an empty array as a response. - - var entries = db.StreamRead(key, id2); + var emptyList = new KeyValuePair[0]; - Assert.True(entries.Length == 0); + Assert.Throws(() => db.StreamRead(emptyList)); } } @@ -924,7 +948,7 @@ public void StreamReadMultipleStreamsWithReadPastSecondStream() new KeyValuePair(key1, "0-0"), // read past the end of stream # 2 - new KeyValuePair(key2, id4) + new KeyValuePair(key2, id4) }; var streams = db.StreamRead(streamList); @@ -958,7 +982,7 @@ public void StreamReadMultipleStreamsWithEmptyResponse() { // Read past the end of both streams. new KeyValuePair(key1, id2), - new KeyValuePair(key2, id4) + new KeyValuePair(key2, id4) }; var streams = db.StreamRead(streamList); @@ -968,6 +992,232 @@ public void StreamReadMultipleStreamsWithEmptyResponse() } } + [Fact] + public void StreamReadPastEndOfStream() + { + var key = GetUniqueKey("read_empty"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + // Read after the final ID in the stream, we expect an empty array as a response. + + var entries = db.StreamRead(key, id2); + + Assert.True(entries.Length == 0); + } + } + + [Fact] + public void StreamReadRange() + { + var key = GetUniqueKey("range"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue); + + Assert.Equal(2, entries.Length); + Assert.Equal(id1, entries[0].Id); + Assert.Equal(id2, entries[1].Id); + } + } + + [Fact] + public void StreamReadRangeOfEmptyStream() + { + var key = GetUniqueKey("range_empty"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var deleted = db.StreamMessagesDelete(key, new RedisValue[] { id1, id2 }); + + var entries = db.StreamRange(key, "-", "+"); + + Assert.Equal(2, deleted); + Assert.NotNull(entries); + Assert.True(entries.Length == 0); + } + } + + [Fact] + public void StreamReadRangeWithCount() + { + var key = GetUniqueKey("range_count"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 1); + + Assert.True(entries.Length == 1); + Assert.Equal(id1, entries[0].Id); + } + } + + [Fact] + public void StreamReadRangeReverse() + { + var key = GetUniqueKey("rangerev"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, messageOrder: Order.Descending); + + Assert.True(entries.Length == 2); + Assert.Equal(id2, entries[0].Id); + Assert.Equal(id1, entries[1].Id); + } + } + + [Fact] + public void StreamReadRangeReverseWithCount() + { + var key = GetUniqueKey("rangerev_count"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + + var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 1, messageOrder: Order.Descending); + + Assert.True(entries.Length == 1); + Assert.Equal(id2, entries[0].Id); + } + } + + [Fact] + public void StreamReadWithAfterIdAndCount_1() + { + var key = GetUniqueKey("read"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + + // Only read a single item from the stream. + var entries = db.StreamRead(key, id1, 1); + + Assert.True(entries.Length == 1); + Assert.Equal(id2, entries[0].Id); + } + } + + [Fact] + public void StreamReadWithAfterIdAndCount_2() + { + var key = GetUniqueKey("read"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "fiedl2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + // Read multiple items from the stream. + var entries = db.StreamRead(key, id1, 2); + + Assert.True(entries.Length == 2); + Assert.Equal(id2, entries[0].Id); + Assert.Equal(id3, entries[1].Id); + } + } + + [Fact] + public void StreamTrimLength() + { + var key = GetUniqueKey("trimlen"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "fiedl2", "value2"); + db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + var numRemoved = db.StreamTrim(key, 1); + var len = db.StreamLength(key); + + Assert.Equal(3, numRemoved); + Assert.Equal(1, len); + } + } + + [Fact] + public void StreamVerifyLength() + { + var key = GetUniqueKey("len"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Add a couple items and check length. + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "fiedl2", "value2"); + + var len = db.StreamLength(key); + + Assert.Equal(2, len); + } + } + private string GetUniqueKey(string type) => $"{type}_stream_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}"; } } diff --git a/StackExchange.Redis.Tests/WrapperBaseTests.cs b/StackExchange.Redis.Tests/WrapperBaseTests.cs index ea52e28a8..c052c44a5 100644 --- a/StackExchange.Redis.Tests/WrapperBaseTests.cs +++ b/StackExchange.Redis.Tests/WrapperBaseTests.cs @@ -750,7 +750,7 @@ public void StreamAcknowledgeAsync_1() [Fact] public void StreamAcknowledgeAsync_2() { - var messageIds = new string[] { "0-0", "0-1", "0-2" }; + var messageIds = new RedisValue[] { "0-0", "0-1", "0-2" }; wrapper.StreamAcknowledgeAsync("key", "group", messageIds, CommandFlags.HighPriority); mock.Verify(_ => _.StreamAcknowledgeAsync("prefix:key", "group", messageIds, CommandFlags.HighPriority)); } @@ -788,7 +788,7 @@ public void StreamAddAsync_4() [Fact] public void StreamClaimMessagesAsync() { - var messageIds = new string[0]; + var messageIds = new RedisValue[0]; wrapper.StreamClaimMessagesAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); mock.Verify(_ => _.StreamClaimMessagesAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); } @@ -796,7 +796,7 @@ public void StreamClaimMessagesAsync() [Fact] public void StreamClaimMessagesReturningIdsAsync() { - var messageIds = new string[0]; + var messageIds = new RedisValue[0]; wrapper.StreamClaimMessagesReturningIdsAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); mock.Verify(_ => _.StreamClaimMessagesReturningIdsAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); } @@ -839,7 +839,7 @@ public void StreamLengthAsync() [Fact] public void StreamMessagesDeleteAsync() { - var messageIds = new string[0] { }; + var messageIds = new RedisValue[0] { }; wrapper.StreamMessagesDeleteAsync("key", messageIds, CommandFlags.HighPriority); mock.Verify(_ => _.StreamMessagesDeleteAsync("prefix:key", messageIds, CommandFlags.HighPriority)); } @@ -852,79 +852,36 @@ public void StreamPendingInfoGetAsync() } [Fact] - public void StreamPendingMessageInfoGetAsync_1() + public void StreamPendingMessageInfoGetAsync() { - wrapper.StreamPendingMessageInfoGetAsync("key", "group", "-", "+", 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingMessageInfoGetAsync("prefix:key", "group", "-", "+", 10, CommandFlags.HighPriority)); + wrapper.StreamPendingMessageInfoGetAsync("key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessageInfoGetAsync("prefix:key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority)); } [Fact] - public void StreamPendingMessageInfoGetAsync_2() + public void StreamRangeAsync() { - wrapper.StreamPendingMessageInfoGetAsync("key", "group", "-", "+", 10, "consumer", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingMessageInfoGetAsync("prefix:key", "group", "-", "+", 10, "consumer", CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRangeAsync_1() - { - wrapper.StreamRangeAsync("key", "-", "+", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRangeAsync("prefix:key", "-", "+", CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRangeAsync_2() - { - wrapper.StreamRangeAsync("key", "-", "+", 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRangeAsync("prefix:key", "-", "+", 10, CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRangeReverseAsync_1() - { - wrapper.StreamRangeReverseAsync("key", "+", "-", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRangeReverseAsync("prefix:key", "+", "-", CommandFlags.HighPriority)); - } - - [Fact] - public void StreamRangeReverseAsync_2() - { - wrapper.StreamRangeReverseAsync("key", "+", "-", 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamRangeReverseAsync("prefix:key", "+", "-", 10, CommandFlags.HighPriority)); + wrapper.StreamRangeAsync("key", "-", "+", null, Order.Ascending, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamRangeAsync("prefix:key", "-", "+", null, Order.Ascending, CommandFlags.HighPriority)); } [Fact] public void StreamReadAsync_1() { var keysAndIds = new KeyValuePair[0] { }; - wrapper.StreamReadAsync(keysAndIds, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamReadAsync(keysAndIds, CommandFlags.HighPriority)); + wrapper.StreamReadAsync(keysAndIds, null, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamReadAsync(keysAndIds, null, CommandFlags.HighPriority)); } [Fact] public void StreamReadAsync_2() { - wrapper.StreamReadAsync("key", "0-0", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamReadAsync("prefix:key", "0-0", CommandFlags.HighPriority)); - } - - [Fact] - public void StreamReadAsync_3() - { - var keysAndIds = new KeyValuePair[0] { }; - wrapper.StreamReadAsync(keysAndIds, 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamReadAsync(keysAndIds, 10, CommandFlags.HighPriority)); - } - - [Fact] - public void StreamReadAsync_4() - { - wrapper.StreamReadAsync("key", "0-0", 10, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamReadAsync("prefix:key", "0-0", 10, CommandFlags.HighPriority)); + wrapper.StreamReadAsync("key", "0-0", null, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamReadAsync("prefix:key", "0-0", null, CommandFlags.HighPriority)); } [Fact] - public void StreamStreamReadGroupAsync() + public void StreamReadGroupAsync() { wrapper.StreamReadGroupAsync("key", "group", "consumer", "0-0", 10, CommandFlags.HighPriority); mock.Verify(_ => _.StreamReadGroupAsync("prefix:key", "group", "consumer", "0-0", 10, CommandFlags.HighPriority)); diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs index 97bc4a714..078ae26c5 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs @@ -1398,7 +1398,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro - long StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None); + long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None); /// /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. @@ -1409,7 +1409,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro - long StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None); + long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1421,7 +1421,7 @@ IEnumerable SortedSetScan(RedisKey key, /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. /// The ID of the newly created message. /// https://redis.io/commands/xadd - string StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1434,7 +1434,7 @@ IEnumerable SortedSetScan(RedisKey key, /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. /// The ID of the newly created message. /// https://redis.io/commands/xadd - string StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1448,7 +1448,7 @@ IEnumerable SortedSetScan(RedisKey key, /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. /// The ID of the newly created message. /// https://redis.io/commands/xadd - string StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1461,7 +1461,7 @@ IEnumerable SortedSetScan(RedisKey key, /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. /// The ID of the newly created message. /// https://redis.io/commands/xadd - string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). @@ -1470,11 +1470,11 @@ IEnumerable SortedSetScan(RedisKey key, /// The consumer group. /// The consumer claiming the given message(s). /// The minimum message idle time to allow the reassignment of the message(s). - /// The ID(s) of the message(s) to claim for the given consumer. + /// The IDs of the messages to claim for the given consumer. /// The flags to use for this operation. /// The messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro - RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); + RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the IDs for the claimed message(s). @@ -1487,7 +1487,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The message IDs for the messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro - string[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); + RedisValue[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Create a consumer group for the given stream. @@ -1541,11 +1541,11 @@ IEnumerable SortedSetScan(RedisKey key, /// Delete messages in the stream. /// /// The key of the stream. - /// The ID(s) of the message(s) to delete. + /// The IDs of the messages to delete. /// The flags to use for this operation. /// Returns the number of messages successfully deleted from the stream. /// https://redis.io/topics/streams-intro - long StreamMessagesDelete(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None); + long StreamMessagesDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// View information about pending messages for a stream. A pending message is a message read using StreamReadGroup (XREADGROUP) but not yet acknowledged. @@ -1566,21 +1566,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of pending messages to return. - /// The flags to use for this operation. - /// An instance of for each pending message. - /// Equivalent of calling XPENDING key group start-id end-id count. - /// https://redis.io/commands/xpending - StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); - - /// - /// View information about each pending message. - /// - /// The key of the stream. - /// The name of the consumer group. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The maximum number of pending messages to return. - /// The consumer name. + /// The consumer name for the pending messages. Pass RedisValue.Null to include pending messages for all consumers. /// The flags to use for this operation. /// An instance of for each pending message. /// Equivalent of calling XPENDING key group start-id end-id count consumer-name. @@ -1593,56 +1579,12 @@ IEnumerable SortedSetScan(RedisKey key, /// The key of the stream. /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The flags to use for this operation. - /// Returns an instance of for each message returned. - /// https://redis.io/commands/xrange - RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None); - - /// - /// Read a stream in reverse order using the given range of IDs. - /// - /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum number of messages to return. - /// The flags to use for this operation. - /// Returns an instance of for each message returned. - /// https://redis.io/commands/xrevrange - RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); - - /// - /// Read a stream in reverse order using the given range of IDs. - /// - /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The maximum number of messages to return. + /// The order of the messages. will execute XRANGE and wil execute XREVRANGE. /// The flags to use for this operation. /// Returns an instance of for each message returned. - /// https://redis.io/commands/xrevrange - RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None); - - /// - /// Read a stream in reverse order using the given range of IDs. - /// - /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The flags to use for this operation. - /// Returns an instance of for each message returned. - /// https://redis.io/commands/xrevrange - RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None); - - /// - /// Read from a single stream. - /// - /// The key of the stream. - /// The ID from within the stream to begin reading. - /// The flags to use for this operation. - /// Returns an instance of for each message returned. - /// Equivalent of calling XREAD STREAMS key id. - /// https://redis.io/commands/xread - RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None); + /// https://redis.io/commands/xrange + RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None); /// /// Read from a single stream. @@ -1654,28 +1596,18 @@ IEnumerable SortedSetScan(RedisKey key, /// Returns an instance of for each message returned. /// Equivalent of calling XREAD COUNT num STREAMS key id. /// https://redis.io/commands/xread - RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None); - - /// - /// Read from multiple streams. - /// - /// The list of streams and the ID from which to begin reading for each stream. - /// The flags to use for this operation. - /// An instance of for each stream. - /// Equivalent of calling XREAD STREAMS key1 key2 id1 id2. - /// https://redis.io/commands/xread - RedisStream[] StreamRead(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None); + RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int? count = null, CommandFlags flags = CommandFlags.None); /// /// Read from multiple streams. /// - /// The list of streams and the ID from which to begin reading for each stream. + /// The list of streams and the ID from which to begin reading for each stream. /// The maximum number of messages to return from each stream. /// The flags to use for this operation. /// An instance of for each stream. /// Equivalent of calling XREAD COUNT num STREAMS key1 key2 id1 id2. /// https://redis.io/commands/xread - RedisStream[] StreamRead(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None); + RedisStream[] StreamRead(IList> streamAndIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None); /// /// Read messages from a stream and an associated consumer group. @@ -1699,7 +1631,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The number of messages removed from the stream. /// https://redis.io/topics/streams-intro - RedisValue StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + long StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs index 8ee67eae9..0d58a51b7 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs @@ -1308,7 +1308,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro - Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None); + Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None); /// /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. @@ -1319,7 +1319,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The number of messages acknowledged. /// https://redis.io/topics/streams-intro - Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None); + Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1331,7 +1331,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1344,7 +1344,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1358,7 +1358,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1371,20 +1371,20 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). /// /// The key of the stream. /// The consumer group. - /// The consumer claiming the given message(s). + /// The consumer claiming the given messages. /// The minimum message idle time to allow the reassignment of the message(s). - /// The ID(s) of the message(s) to claim for the given consumer. + /// The IDs of the messages to claim for the given consumer. /// The flags to use for this operation. /// The messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro - Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); + Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the IDs for the claimed message(s). @@ -1397,7 +1397,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The message IDs for the messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro - Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None); + Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Create a consumer group for the given stream. @@ -1451,11 +1451,11 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// Delete messages in the stream. /// /// The key of the stream. - /// The ID(s) of the message(s) to delete. + /// The IDs of the messages to delete. /// The flags to use for this operation. /// Returns the number of messages successfully deleted from the stream. /// https://redis.io/topics/streams-intro - Task StreamMessagesDeleteAsync(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None); + Task StreamMessagesDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// View information about pending messages for a stream. A pending message is a message read using StreamReadGroup (XREADGROUP) but not yet acknowledged. @@ -1476,38 +1476,13 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of pending messages to return. - /// The flags to use for this operation. - /// An instance of for each pending message. - /// Equivalent of calling XPENDING key group start-id end-id count. - /// https://redis.io/commands/xpending - Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); - - /// - /// View information about each pending message. - /// - /// The key of the stream. - /// The name of the consumer group. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The maximum number of pending messages to return. - /// The consumer name. + /// The consumer name for the pending messages. Pass RedisValue.Null to include pending messages for all consumers. /// The flags to use for this operation. /// An instance of for each pending message. /// Equivalent of calling XPENDING key group start-id end-id count consumer-name. /// https://redis.io/commands/xpending Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); - /// - /// Read a stream using the given range of IDs. - /// - /// The key of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The flags to use for this operation. - /// Returns an instance of for each message returned. - /// https://redis.io/commands/xrange - Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None); - /// /// Read a stream using the given range of IDs. /// @@ -1515,44 +1490,11 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of messages to return. + /// The order of the messages. will execute XRANGE and wil execute XREVRANGE. /// The flags to use for this operation. /// Returns an instance of for each message returned. /// https://redis.io/commands/xrange - Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None); - - /// - /// Read a stream in reverse order using the given range of IDs. - /// - /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum number of messages to return. - /// The flags to use for this operation. - /// Returns an instance of for each message returned. - /// https://redis.io/commands/xrevrange - Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None); - - /// - /// Read a stream in reverse order using the given range of IDs. - /// - /// The key of the stream. - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The flags to use for this operation. - /// Returns an instance of for each message returned. - /// https://redis.io/commands/xrevrange - Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None); - - /// - /// Read from a single stream. - /// - /// The key of the stream. - /// The ID from within the stream to begin reading. - /// The flags to use for this operation. - /// Returns an instance of for each message returned. - /// Equivalent of calling XREAD STREAMS key id. - /// https://redis.io/commands/xread - Task StreamReadAsync(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None); + Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None); /// /// Read from a single stream. @@ -1564,28 +1506,18 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// Returns an instance of for each message returned. /// Equivalent of calling XREAD COUNT num STREAMS key id. /// https://redis.io/commands/xread - Task StreamReadAsync(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None); - - /// - /// Read from multiple streams. - /// - /// The list of streams and the ID from which to begin reading for each stream. - /// The flags to use for this operation. - /// An instance of for each stream. - /// Equivalent of calling XREAD STREAMS key1 key2 id1 id2. - /// https://redis.io/commands/xread - Task StreamReadAsync(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None); + Task StreamReadAsync(RedisKey key, RedisValue afterId, int? count = null, CommandFlags flags = CommandFlags.None); /// /// Read from multiple streams. /// - /// The list of streams and the ID from which to begin reading for each stream. + /// The list of streams and the ID from which to begin reading for each stream. /// The maximum number of messages to return from each stream. /// The flags to use for this operation. /// An instance of for each stream. /// Equivalent of calling XREAD COUNT num STREAMS key1 key2 id1 id2. /// https://redis.io/commands/xread - Task StreamReadAsync(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None); + Task StreamReadAsync(IList> streamAndIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None); /// /// Read messages from a stream and an associated consumer group. @@ -1609,7 +1541,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The number of messages removed from the stream. /// https://redis.io/topics/streams-intro - Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs index 801de7cad..863877eff 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -587,42 +587,42 @@ public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue return Inner.SortedSetScore(ToInner(key), member, flags); } - public long StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) { return Inner.StreamAcknowledge(ToInner(key), groupName, messageId, flags); } - public long StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamAcknowledge(ToInner(key), groupName, messageIds, flags); } - public string StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAdd(ToInner(key), streamPairs, maxLength, useApproximateMaxLength, flags); } - public string StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAdd(ToInner(key), streamField, streamValue, maxLength, useApproximateMaxLength, flags); } - public string StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAdd(ToInner(key), streamEntryId, streamField, streamValue, maxLength, useApproximateMaxLength, flags); } - public string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAdd(ToInner(key), streamEntryId, streamPairs, maxLength, useApproximateMaxLength, flags); } - public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamClaimMessages(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } - public string[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + public RedisValue[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamClaimMessagesReturningIds(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } @@ -652,7 +652,7 @@ public long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None) return Inner.StreamLength(ToInner(key), flags); } - public long StreamMessagesDelete(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None) + public long StreamMessagesDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamMessagesDelete(ToInner(key), messageIds, flags); } @@ -662,52 +662,22 @@ public StreamPendingInfo StreamPendingInfoGet(RedisKey key, RedisValue groupName return Inner.StreamPendingInfoGet(ToInner(key), groupName, flags); } - public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamPendingMessageInfoGet(ToInner(key), groupName, minId, maxId, count, flags); - } - public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) { return Inner.StreamPendingMessageInfoGet(ToInner(key), groupName, minId, maxId, count, consumerName, flags); } - public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamRange(ToInner(key), minId, maxId, flags); - } - - public RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamRangeReverse(ToInner(key), maxId, minId, count, flags); - } - - public RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - return Inner.StreamRangeReverse(ToInner(key), maxId, minId, flags); + return Inner.StreamRange(ToInner(key), minId, maxId, count, order, flags); } - public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamRange(ToInner(key), minId, maxId, count, flags); - } - - public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamRead(ToInner(key), afterId, flags); - } - - public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int? count = null, CommandFlags flags = CommandFlags.None) { return Inner.StreamRead(ToInner(key), afterId, count, flags); } - public RedisStream[] StreamRead(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamRead(streamWithAfterIdList, flags); - } - - public RedisStream[] StreamRead(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) + public RedisStream[] StreamRead(IList> streamWithAfterIdList, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { return Inner.StreamRead(streamWithAfterIdList, countPerStream, flags); } @@ -717,7 +687,7 @@ public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, Re return Inner.StreamReadGroup(ToInner(key), groupName, consumerName, readFromId, count, flags); } - public RedisValue StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public long StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamTrim(ToInner(key), maxLength, useApproximateMaxLength, flags); } diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs index 2b1e23bdd..f14112ab8 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs @@ -566,42 +566,42 @@ public Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, return Inner.SortedSetScoreAsync(ToInner(key), member, flags); } - public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) { return Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageId, flags); } - public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageIds, flags); } - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAddAsync(ToInner(key), streamPairs, maxLength, useApproximateMaxLength, flags); } - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAddAsync(ToInner(key), streamField, streamValue, maxLength, useApproximateMaxLength, flags); } - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAddAsync(ToInner(key), streamEntryId, streamField, streamValue, maxLength, useApproximateMaxLength, flags); } - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAddAsync(ToInner(key), streamEntryId, streamPairs, maxLength, useApproximateMaxLength, flags); } - public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamClaimMessagesAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } - public Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamClaimMessagesReturningIdsAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } @@ -631,7 +631,7 @@ public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFl return Inner.StreamLengthAsync(ToInner(key), flags); } - public Task StreamMessagesDeleteAsync(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamMessagesDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { return Inner.StreamMessagesDeleteAsync(ToInner(key), messageIds, flags); } @@ -641,52 +641,22 @@ public Task StreamPendingInfoGetAsync(RedisKey key, RedisValu return Inner.StreamPendingInfoGetAsync(ToInner(key), groupName, flags); } - public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamPendingMessageInfoGetAsync(ToInner(key), groupName, minId, maxId, count, flags); - } - public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) { return Inner.StreamPendingMessageInfoGetAsync(ToInner(key), groupName, minId, maxId, count, consumerName, flags); } - public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamRangeAsync(ToInner(key), minId, maxId, flags); - } - - public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamRangeAsync(ToInner(key), minId, maxId, count, flags); - } - - public Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None) + public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - return Inner.StreamRangeReverseAsync(ToInner(key), maxId, minId, count, flags); + return Inner.StreamRangeAsync(ToInner(key), minId, maxId, count, order, flags); } - public Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamRangeReverseAsync(ToInner(key), maxId, minId, flags); - } - - public Task StreamReadAsync(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamReadAsync(ToInner(key), afterId, flags); - } - - public Task StreamReadAsync(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) + public Task StreamReadAsync(RedisKey key, RedisValue afterId, int? count = null, CommandFlags flags = CommandFlags.None) { return Inner.StreamReadAsync(ToInner(key), afterId, count, flags); } - public Task StreamReadAsync(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamReadAsync(streamWithAfterIdList, flags); - } - - public Task StreamReadAsync(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) + public Task StreamReadAsync(IList> streamWithAfterIdList, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { return Inner.StreamReadAsync(streamWithAfterIdList, countPerStream, flags); } @@ -696,7 +666,7 @@ public Task StreamReadGroupAsync(RedisKey key, RedisValue gr return Inner.StreamReadGroupAsync(ToInner(key), groupName, consumerName, readFromId, count, flags); } - public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamTrimAsync(ToInner(key), maxLength, useApproximateMaxLength, flags); } diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index 65f08abf4..5f74b43ec 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -1579,147 +1579,176 @@ IEnumerable IDatabase.SortedSetScan(RedisKey key, RedisValue pat return ExecuteAsync(msg, ResultProcessor.NullableDouble); } - public long StreamAcknowledge(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageId); + var msg = GetStreamAcknowledgeMessage(key, groupName, messageId, flags); return ExecuteSync(msg, ResultProcessor.Int64); } - public long StreamAcknowledge(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageIds); - return ExecuteSync(msg, ResultProcessor.Int64); + var msg = GetStreamAcknowledgeMessage(key, groupName, messageId, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); } - public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string messageId, CommandFlags flags = CommandFlags.None) + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageId); - return ExecuteAsync(msg, ResultProcessor.Int64); + var msg = GetStreamAcknowledgeMessage(key, groupName, messageIds, flags); + return ExecuteSync(msg, ResultProcessor.Int64); } - public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAcknowledgeMessage(key, groupName, flags, messageIds); + var msg = GetStreamAcknowledgeMessage(key, groupName, messageIds, flags); return ExecuteAsync(msg, ResultProcessor.Int64); } - public string StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - StreamConstants.AutoGeneratedId, - maxLength, - useApproximateMaxLength, - flags, - streamPairs); + StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + streamPairs, + flags); - return ExecuteSync(msg, ResultProcessor.String); + return ExecuteSync(msg, ResultProcessor.RedisValue); } - public string StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - StreamConstants.AutoGeneratedId, - maxLength, useApproximateMaxLength, - flags, - new NameValueEntry(streamField, streamValue)); + StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + streamPairs, + flags); - return ExecuteSync(msg, ResultProcessor.String); + return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public string StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - streamEntryId, - maxLength, - useApproximateMaxLength, - flags, - new NameValueEntry(streamField, streamValue)); + StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + new NameValueEntry(streamField, streamValue), + flags); - return ExecuteSync(msg, ResultProcessor.String); + return ExecuteSync(msg, ResultProcessor.RedisValue); } - public string StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - streamEntryId, - maxLength, - useApproximateMaxLength, - flags, - streamPairs); + StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + new NameValueEntry(streamField, streamValue), + flags); - return ExecuteSync(msg, ResultProcessor.String); + return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - StreamConstants.AutoGeneratedId, - maxLength, - useApproximateMaxLength, - flags, - streamPairs); + streamEntryId, + maxLength, + useApproximateMaxLength, + new NameValueEntry(streamField, streamValue), + flags); - return ExecuteAsync(msg, ResultProcessor.String); + return ExecuteSync(msg, ResultProcessor.RedisValue); } - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - StreamConstants.AutoGeneratedId, - maxLength, - useApproximateMaxLength, - flags, - new NameValueEntry(streamField, streamValue)); + streamEntryId, + maxLength, + useApproximateMaxLength, + new NameValueEntry(streamField, streamValue), + flags); - return ExecuteAsync(msg, ResultProcessor.String); + return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - streamEntryId, - maxLength, - useApproximateMaxLength, - flags, - new NameValueEntry(streamField, streamValue)); + streamEntryId, + maxLength, + useApproximateMaxLength, + streamPairs, + flags); - return ExecuteAsync(msg, ResultProcessor.String); + return ExecuteSync(msg, ResultProcessor.RedisValue); } - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - streamEntryId, - maxLength, - useApproximateMaxLength, - flags, - streamPairs); + streamEntryId, + maxLength, + useApproximateMaxLength, + streamPairs, + flags); - return ExecuteAsync(msg, ResultProcessor.String); + return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, flags, messageIds); + var msg = GetStreamClaimMessage(key, + consumerGroup, + claimingConsumer, + minIdleTimeInMs, + messageIds, + returnJustIds: false, + flags: flags); + return ExecuteSync(msg, ResultProcessor.SingleStream); } - public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, flags, messageIds); + var msg = GetStreamClaimMessage(key, + consumerGroup, + claimingConsumer, + minIdleTimeInMs, + messageIds, + returnJustIds: false, + flags: flags); + return ExecuteAsync(msg, ResultProcessor.SingleStream); } - public string[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + public RedisValue[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, flags, messageIds, true); - return ExecuteSync(msg, ResultProcessor.StringArray); + var msg = GetStreamClaimMessage(key, + consumerGroup, + claimingConsumer, + minIdleTimeInMs, + messageIds, + returnJustIds: true, + flags: flags); + + return ExecuteSync(msg, ResultProcessor.RedisValueArray); } - public Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, flags, messageIds, true); - return ExecuteAsync(msg, ResultProcessor.StringArray); + var msg = GetStreamClaimMessage(key, + consumerGroup, + claimingConsumer, + minIdleTimeInMs, + messageIds, + returnJustIds: true, + flags: flags); + + return ExecuteAsync(msg, ResultProcessor.RedisValueArray); } public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) @@ -1727,7 +1756,7 @@ public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisV var msg = Message.Create(Database, flags, RedisCommand.XGROUP, - new RedisValue[] + new RedisValue[4] { StreamConstants.Create, key.AsRedisValue(), @@ -1752,25 +1781,31 @@ public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupN public StreamConsumerInfo[] StreamConsumerInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XINFO, + var msg = Message.Create(Database, + flags, + RedisCommand.XINFO, new RedisValue[3] { StreamConstants.Consumers, key.AsRedisValue(), groupName }); + return ExecuteSync(msg, ResultProcessor.StreamConsumerInfo); } public Task StreamConsumerInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XINFO, + var msg = Message.Create(Database, + flags, + RedisCommand.XINFO, new RedisValue[3] { StreamConstants.Consumers, key.AsRedisValue(), groupName }); + return ExecuteAsync(msg, ResultProcessor.StreamConsumerInfo); } @@ -1810,24 +1845,24 @@ public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFl return ExecuteAsync(msg, ResultProcessor.Int64); } - public long StreamMessagesDelete(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None) + public long StreamMessagesDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.XDEL, key, - messageIds.ToRedisValueArray()); + messageIds); return ExecuteSync(msg, ResultProcessor.Int64); } - public Task StreamMessagesDeleteAsync(RedisKey key, string[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamMessagesDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.XDEL, key, - messageIds.ToRedisValueArray()); + messageIds); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1844,282 +1879,51 @@ public Task StreamPendingInfoGetAsync(RedisKey key, RedisValu return ExecuteAsync(msg, ResultProcessor.StreamPendingInfo); } - public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) - { - var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, RedisValue.Null, flags); - return ExecuteSync(msg, ResultProcessor.StreamPendingMessages); - } - public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) { var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, consumerName, flags); return ExecuteSync(msg, ResultProcessor.StreamPendingMessages); } - public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) - { - var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, RedisValue.Null, flags); - return ExecuteAsync(msg, ResultProcessor.StreamPendingMessages); - } - public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) { var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, consumerName, flags); return ExecuteAsync(msg, ResultProcessor.StreamPendingMessages); } - public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) { - // Example: > XRANGE somestream - + - - var msg = Message.Create(Database, - flags, - RedisCommand.XRANGE, - key, - minId, - maxId); - + var msg = GetStreamRangeMessage(key, minId, maxId, count, messageOrder, flags); return ExecuteSync(msg, ResultProcessor.SingleStream); } - public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) { - if (count <= 0) - { - throw new ArgumentException("'count' must be greater than 0."); - } - - var msg = Message.Create(Database, - flags, - RedisCommand.XRANGE, - key, - new RedisValue[] - { - minId, - maxId, - StreamConstants.Count, - count - }); - - return ExecuteSync(msg, ResultProcessor.SingleStream); - } - - public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, - flags, - RedisCommand.XRANGE, - key, - new RedisValue[] - { - minId, - maxId - }); - + var msg = GetStreamRangeMessage(key, minId, maxId, count, messageOrder, flags); return ExecuteAsync(msg, ResultProcessor.SingleStream); } - public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int count, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int? count = null, CommandFlags flags = CommandFlags.None) { - if (count <= 0) - { - throw new ArgumentException("'count' must be greater than 0."); - } - - var msg = Message.Create(Database, - flags, - RedisCommand.XRANGE, - key, - new RedisValue[] - { - minId, - maxId, - StreamConstants.Count, - count - }); - - return ExecuteAsync(msg, ResultProcessor.SingleStream); - } - - public RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None) - { - // Example: > XRANGE somestream + - COUNT 1 - - if (count <= 0) - { - throw new ArgumentException("'count' must be greater than 0."); - } - - var msg = Message.Create(Database, - flags, - RedisCommand.XREVRANGE, - key, - new RedisValue[] - { - maxId, - minId, - StreamConstants.Count, - count - }); - - return ExecuteSync(msg, ResultProcessor.SingleStream); - } - - public RedisStreamEntry[] StreamRangeReverse(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None) - { - // Example: > XRANGE somestream + - - - var msg = Message.Create(Database, - flags, - RedisCommand.XREVRANGE, - key, - maxId, - minId); - - return ExecuteSync(msg, ResultProcessor.SingleStream); - } - - public Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, int count, CommandFlags flags = CommandFlags.None) - { - if (count <= 0) - { - throw new ArgumentException("'count' must be greater than 0."); - } - - var msg = Message.Create(Database, - flags, - RedisCommand.XREVRANGE, - key, - new RedisValue[] - { - maxId, - minId, - StreamConstants.Count, - count - }); - - return ExecuteAsync(msg, ResultProcessor.SingleStream); - } - - public Task StreamRangeReverseAsync(RedisKey key, RedisValue maxId, RedisValue minId, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, - flags, - RedisCommand.XREVRANGE, - key, - maxId, - minId); - - return ExecuteAsync(msg, ResultProcessor.SingleStream); - } - - public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None) - { - // Example: > XREAD STREAMS writers 1526999352406-0 - var msg = Message.Create(Database, - flags, - RedisCommand.XREAD, - new RedisValue[] - { - StreamConstants.Streams, - key.AsRedisValue(), - afterId - }); - - return ExecuteSync(msg, ResultProcessor.SingleStreamWithNameSkip); - } - - public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) - { - if (count <= 0) - { - throw new ArgumentException("'count' must be greater than 0."); - } - - // Example: > XREAD COUNT 2 STREAMS writers 1526999352406-0 - var msg = Message.Create(Database, - flags, - RedisCommand.XREAD, - new RedisValue[] - { - StreamConstants.Count, - count, - StreamConstants.Streams, - key.AsRedisValue(), - afterId - }); - + var msg = GetSingleStreamReadMessage(key, afterId, count, flags); return ExecuteSync(msg, ResultProcessor.SingleStreamWithNameSkip); } - public RedisStream[] StreamRead(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None) + public Task StreamReadAsync(RedisKey key, RedisValue afterId, int? count = null, CommandFlags flags = CommandFlags.None) { - var msg = GetMultiStreamReadMessage(streamWithAfterIdList, flags); - return ExecuteSync(msg, ResultProcessor.MultiStream); - } - - public RedisStream[] StreamRead(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) - { - if (countPerStream <= 0) - { - throw new ArgumentException("'countPerStream' must be greater than 0."); - } - - var msg = GetMultiStreamReadMessage(streamWithAfterIdList, countPerStream, flags); - return ExecuteSync(msg, ResultProcessor.MultiStream); - } - - public Task StreamReadAsync(RedisKey key, RedisValue afterId, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, - flags, - RedisCommand.XREAD, - new RedisValue[] - { - StreamConstants.Streams, - key.AsRedisValue(), - afterId - }); - - return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip); - } - - public Task StreamReadAsync(RedisKey key, RedisValue afterId, int count, CommandFlags flags = CommandFlags.None) - { - if (count <= 0) - { - throw new ArgumentException("'count' must be greater than 0."); - } - - var msg = Message.Create(Database, - flags, - RedisCommand.XREAD, - new RedisValue[] - { - StreamConstants.Count, - count, - StreamConstants.Streams, - key.AsRedisValue(), - afterId - }); - + var msg = GetSingleStreamReadMessage(key, afterId, count, flags); return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip); } - public Task StreamReadAsync(IList> streamWithAfterIdList, CommandFlags flags = CommandFlags.None) + public RedisStream[] StreamRead(IList> streamAndIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { - var msg = GetMultiStreamReadMessage(streamWithAfterIdList, flags); - return ExecuteAsync(msg, ResultProcessor.MultiStream); + var msg = GetMultiStreamReadMessage(streamAndIdPairs, countPerStream, flags); + return ExecuteSync(msg, ResultProcessor.MultiStream); } - public Task StreamReadAsync(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags = CommandFlags.None) + public Task StreamReadAsync(IList> streamAndIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { - if (countPerStream <= 0) - { - throw new ArgumentException("'countPerStream' must be greater than 0."); - } - - var msg = GetMultiStreamReadMessage(streamWithAfterIdList, countPerStream, flags); + var msg = GetMultiStreamReadMessage(streamAndIdPairs, countPerStream, flags); return ExecuteAsync(msg, ResultProcessor.MultiStream); } @@ -2131,49 +1935,20 @@ public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, Re public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) { - if (count <= 0) - { - throw new ArgumentException("'count' must be greater than 0."); - } - var msg = GetStreamReadGroupMessage(key, groupName, consumerName, readFromId, count, flags); return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip); } - public RedisValue StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public long StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - if (maxLength <= 0) - { - throw new ArgumentException("'maxLength' must be greater than 0."); - } - - var msg = Message.Create(Database, - flags, - RedisCommand.XTRIM, - key, - StreamConstants.MaxLen, - useApproximateMaxLength ? StreamConstants.ApproximateMaxLen : StreamConstants.Blank, - maxLength); - - return ExecuteSync(msg, ResultProcessor.RedisValue); + var msg = GetStreamTrimMessage(key, maxLength, useApproximateMaxLength, flags); + return ExecuteSync(msg, ResultProcessor.Int64); } - public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - if (maxLength <= 0) - { - throw new ArgumentException("'maxLength' must be greater than 0."); - } - - var msg = Message.Create(Database, - flags, - RedisCommand.XTRIM, - key, - StreamConstants.MaxLen, - useApproximateMaxLength ? StreamConstants.ApproximateMaxLen : StreamConstants.Blank, - maxLength); - - return ExecuteAsync(msg, ResultProcessor.RedisValue); + var msg = GetStreamTrimMessage(key, maxLength, useApproximateMaxLength, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); } public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) @@ -2533,43 +2308,57 @@ private RedisValue GetLexRange(RedisValue value, Exclude exclude, bool isStart) return result; } - private Message GetMultiStreamReadMessage(IList> streamWithAfterIdList, CommandFlags flags) + private Message GetMultiStreamReadMessage(IList> streamAndIdPairs, int? countPerStream, CommandFlags flags) { - // See: https://redis.io/commands/xread. + // Example: XREAD COUNT 2 STREAMS mystream writers 0-0 0-0 - // + 1 because we need to make room for the STREAMS key word in the XREAD command. - var values = new RedisValue[(streamWithAfterIdList.Count * 2) + 1]; - var idOffset = streamWithAfterIdList.Count + 1; + if (streamAndIdPairs == null) throw new ArgumentNullException(nameof(streamAndIdPairs)); + if (streamAndIdPairs.Count == 0) throw new ArgumentOutOfRangeException(nameof(streamAndIdPairs), "streamAndIdPairs must contain at least one item."); - values[0] = StreamConstants.Streams; + if (countPerStream.HasValue && countPerStream <= 0) + { + throw new ArgumentOutOfRangeException(nameof(countPerStream), "countPerStream must be greater than 0."); + } + + var values = new RedisValue[ + 1 // Streams keyword. + + (streamAndIdPairs.Count * 2) // Room for the stream names and the ID from which to begin reading. + + (countPerStream.HasValue ? 2 : 0)]; // Room for "COUNT num" or 0 if countPerStream is null. - // The ID for a given stream is offset by the stream count plus the associated stream ID count. + var offset = 0; - // Format: stream1 stream2 afterId1 afterId2 - for (var i = 0; i < streamWithAfterIdList.Count; i++) + if (countPerStream.HasValue) { - values[i + 1] = streamWithAfterIdList[i].Key.AsRedisValue(); - values[idOffset++] = streamWithAfterIdList[i].Value; + values[offset++] = StreamConstants.Count; + values[offset++] = countPerStream; } - return Message.Create(Database, flags, RedisCommand.XREAD, values); - } + values[offset++] = StreamConstants.Streams; - private Message GetMultiStreamReadMessage(IList> streamWithAfterIdList, int countPerStream, CommandFlags flags) - { - var values = new RedisValue[(streamWithAfterIdList.Count * 2) + 3]; - var idOffset = streamWithAfterIdList.Count + 3; + // Write the stream names and the message IDs from which to read for the associated stream. Each pair + // will be separated by an offset of the index of the stream name plus the pair count. + + /* + * [0] = COUNT + * [1] = 2 + * [3] = STREAMS + * [4] = stream1 + * [5] = stream2 + * [6] = stream3 + * [7] = id1 + * [8] = id2 + * [9] = id3 + * + * */ - values[0] = StreamConstants.Count; - values[1] = countPerStream; - values[2] = StreamConstants.Streams; + var pairCount = streamAndIdPairs.Count; - // Duplicated from the overloaded method above. - // Is it worth refactoring the name and ID staggering to a separate method? - for (var i = 0; i < streamWithAfterIdList.Count; i++) + for (var i = 0; i < streamAndIdPairs.Count; i++) { - values[i + 3] = streamWithAfterIdList[i].Key.AsRedisValue(); - values[idOffset++] = streamWithAfterIdList[i].Value; + values[offset] = streamAndIdPairs[i].Key.AsRedisValue(); + values[offset + pairCount] = streamAndIdPairs[i].Value; + + offset++; } return Message.Create(Database, flags, RedisCommand.XREAD, values); @@ -2803,7 +2592,7 @@ private Message GetSortedSetRemoveRangeByScoreMessage(RedisKey key, double start GetRange(start, exclude, true), GetRange(stop, exclude, false)); } - private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, CommandFlags flags, string messageId) + private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags) { var arr = new RedisValue[3] { @@ -2815,8 +2604,11 @@ private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, return Message.Create(Database, flags, RedisCommand.XACK, arr); } - private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, CommandFlags flags, string[] messageIds) + private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags) { + if (messageIds == null) throw new ArgumentNullException(nameof(messageIds)); + if (messageIds.Length == 0) throw new ArgumentOutOfRangeException(nameof(messageIds), "messageIds must contain at least one item."); + var arr = new RedisValue[messageIds.Length + 2]; var offset = 0; @@ -2832,7 +2624,7 @@ private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, return Message.Create(Database, flags, RedisCommand.XACK, arr); } - private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags, NameValueEntry streamPair) + private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLength, bool useApproximateMaxLength, NameValueEntry streamPair, CommandFlags flags) { // Calculate the correct number of arguments: // 3 array elements for Entry ID & NameValueEntry.Name & NameValueEntry.Value. @@ -2867,12 +2659,12 @@ private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLe return Message.Create(Database, flags, RedisCommand.XADD, key, values); } - private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags, NameValueEntry[] streamPairs) + private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLength, bool useApproximateMaxLength, NameValueEntry[] streamPairs, CommandFlags flags) { // See https://redis.io/commands/xadd. if (streamPairs == null) throw new ArgumentNullException(nameof(streamPairs)); - if (streamPairs.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPairs), "streamPairs must contain at least one field."); + if (streamPairs.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPairs), "streamPairs must contain at least one item."); if (maxLength.HasValue && maxLength <= 0) { @@ -2914,12 +2706,10 @@ private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLe return Message.Create(Database, flags, RedisCommand.XADD, key, values); } - private Message GetStreamClaimMessage(RedisKey key, RedisValue consumerGroup, RedisValue assignToConsumer, long minIdleTimeInMs, CommandFlags flags, string[] messageIds, bool returnJustIds = false) + private Message GetStreamClaimMessage(RedisKey key, RedisValue consumerGroup, RedisValue assignToConsumer, long minIdleTimeInMs, RedisValue[] messageIds, bool returnJustIds, CommandFlags flags) { - if (messageIds == null || messageIds.Length == 0) - { - throw new ArgumentException("messageIds must contain at least 1 messageId."); - } + if (messageIds == null) throw new ArgumentNullException(nameof(messageIds)); + if (messageIds.Length == 0) throw new ArgumentOutOfRangeException(nameof(messageIds), "messageIds must contain at least one item."); // XCLAIM ... var values = new RedisValue[4 + messageIds.Length + (returnJustIds ? 1 : 0)]; @@ -2977,9 +2767,34 @@ private Message GetStreamPendingMessagesMessage(RedisKey key, RedisValue groupNa } return Message.Create(Database, - flags, - RedisCommand.XPENDING, - values); + flags, + RedisCommand.XPENDING, + values); + } + + private Message GetStreamRangeMessage(RedisKey key, RedisValue minId, RedisValue maxId, int? count, Order messageOrder, CommandFlags flags) + { + if (count.HasValue && count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "count must be greater than 0."); + } + + var values = new RedisValue[2 + (count.HasValue ? 2 : 0)]; + + values[0] = (messageOrder == Order.Ascending ? minId : maxId); + values[1] = (messageOrder == Order.Ascending ? maxId : minId); + + if (count.HasValue) + { + values[2] = StreamConstants.Count; + values[3] = count.Value; + } + + return Message.Create(Database, + flags, + messageOrder == Order.Ascending ? RedisCommand.XRANGE : RedisCommand.XREVRANGE, + key, + values); } private Message GetStreamReadGroupMessage(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count, CommandFlags flags) @@ -3010,9 +2825,64 @@ private Message GetStreamReadGroupMessage(RedisKey key, RedisValue groupName, Re arr[offset] = readFromId; return Message.Create(Database, - flags, - RedisCommand.XREADGROUP, - arr); + flags, + RedisCommand.XREADGROUP, + arr); + } + + private Message GetSingleStreamReadMessage(RedisKey key, RedisValue afterId, int? count, CommandFlags flags) + { + if (count.HasValue && count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "count must be greater than 0."); + } + + var values = new RedisValue[3 + (count.HasValue ? 2 : 0)]; + var offset = 0; + + if (count.HasValue) + { + values[offset++] = StreamConstants.Count; + values[offset++] = count.Value; + } + + values[offset++] = StreamConstants.Streams; + values[offset++] = key.AsRedisValue(); + values[offset] = afterId; + + // Example: > XREAD COUNT 2 STREAMS writers 1526999352406-0 + return Message.Create(Database, + flags, + RedisCommand.XREAD, + values); + } + + private Message GetStreamTrimMessage(RedisKey key, int maxLength, bool useApproximateMaxLength, CommandFlags flags) + { + if (maxLength <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxLength), "maxLength must be greater than 0."); + } + + var values = new RedisValue[2 + (useApproximateMaxLength ? 1 : 0)]; + + values[0] = StreamConstants.MaxLen; + + if (useApproximateMaxLength) + { + values[1] = StreamConstants.ApproximateMaxLen; + values[2] = maxLength; + } + else + { + values[1] = maxLength; + } + + return Message.Create(Database, + flags, + RedisCommand.XTRIM, + key, + values); } private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags) diff --git a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs b/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs index 82c36145c..df126b39c 100644 --- a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs +++ b/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs @@ -1440,8 +1440,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes // details[0] = Name of the Stream // details[1] = Multibulk Array of Stream Entries - return new RedisStream( - key: details[0].AsRedisKey(), + return new RedisStream(key: details[0].AsRedisKey(), entries: ParseRedisStreamEntries(details[1])); }); @@ -1473,11 +1472,9 @@ protected override StreamConsumerInfo ParseItem(RawResult result) var arr = result.GetItems(); - var consumerName = (string)arr[1].AsRedisValue(); - var pendingCount = (int)arr[3].AsRedisValue(); - var idleTime = (int)arr[5].AsRedisValue(); - - return new StreamConsumerInfo(consumerName, pendingCount, idleTime); + return new StreamConsumerInfo(name: arr[1].AsRedisValue(), + pendingMessageCount: (int)arr[3].AsRedisValue(), + idleTimeInMilliseconds: (long)arr[5].AsRedisValue()); } } @@ -1504,11 +1501,9 @@ protected override StreamGroupInfo ParseItem(RawResult result) var arr = result.GetItems(); - var groupName = (string)arr[1].AsRedisValue(); - var consumerCount = (int)arr[3].AsRedisValue(); - var pendingCount = (int)arr[5].AsRedisValue(); - - return new StreamGroupInfo(groupName, consumerCount, pendingCount); + return new StreamGroupInfo(name: arr[1].AsRedisValue(), + consumerCount: (int)arr[3].AsRedisValue(), + pendingMessageCount: (int)arr[5].AsRedisValue()); } } @@ -1576,14 +1571,12 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes var entries = ParseRedisStreamEntries(RawResult.CreateMultiBulk(arr[9], arr[11])); - var streamInfo = new StreamInfo( - (int)arr[1].AsRedisValue(), // Stream Length - (int)arr[3].AsRedisValue(), // Radix tree keys - (int)arr[5].AsRedisValue(), // Radix tree nodes - (int)arr[7].AsRedisValue(), // Consumer Group Count - entries[0], // First stream entry - entries[1]); // Last stream entry - + var streamInfo = new StreamInfo(length: (int)arr[1].AsRedisValue(), + radixTreeKeys: (int)arr[3].AsRedisValue(), + radixTreeNodes: (int)arr[5].AsRedisValue(), + groups: (int)arr[7].AsRedisValue(), + firstEntry: entries[0], + lastEntry: entries[1]); SetResult(message, streamInfo); return true; @@ -1616,24 +1609,28 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes return false; } - var pendingMessageCount = arr[0].AsRedisValue(); - var lowestPendingMessageId = arr[1].AsRedisValue(); - var highestPendingMessageId = arr[2].AsRedisValue(); + StreamConsumer[] consumers = null; - // Get the list of Consumers - var consumers = Array.ConvertAll(arr[3].GetItems(), item => + // If there are no consumers as of yet for the given group, the last + // item in the response array will be null. + if (!arr[3].IsNull) { - var details = item.GetItems(); + consumers = Array.ConvertAll(arr[3].GetItems(), item => + { + var details = item.GetItems(); - return new StreamConsumer( - name: details[0].AsRedisValue(), - pendingMessageCount: details[1].AsRedisValue()); - }); + return new StreamConsumer( + name: details[0].AsRedisValue(), + pendingMessageCount: (int)details[1].AsRedisValue()); + }); + } - var pendingInfo = new StreamPendingInfo(pendingMessageCount, - lowestPendingMessageId, - highestPendingMessageId, - consumers); + var pendingInfo = new StreamPendingInfo(pendingMessageCount: (int)arr[0].AsRedisValue(), + lowestId: arr[1].AsRedisValue(), + highestId: arr[2].AsRedisValue(), + consumers: consumers ?? new StreamConsumer[0]); + // ^^^^^ + // Should we bother allocating an empty array only to prevent the need for a null check? SetResult(message, pendingInfo); return true; @@ -1655,11 +1652,10 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes { var details = item.GetItems(); - return new StreamPendingMessageInfo( - messageId: details[0].AsRedisValue(), - consumerName: details[1].AsRedisValue(), - idleTimeInMs: details[2].AsRedisValue(), - deliveryCount: details[3].AsRedisValue()); + return new StreamPendingMessageInfo(messageId: details[0].AsRedisValue(), + consumerName: details[1].AsRedisValue(), + idleTimeInMs: (long)details[2].AsRedisValue(), + deliveryCount: (int)details[3].AsRedisValue()); }); SetResult(message, messageInfoArray); @@ -1692,9 +1688,8 @@ protected RedisStreamEntry[] ParseRedisStreamEntries(RawResult result) // [1] = Multibulk array of the name/value pairs of the stream entry's data var entryDetails = item.GetItems(); - return new RedisStreamEntry( - id: entryDetails[0].AsRedisValue(), - values: ParseStreamEntryValues(entryDetails[1])); + return new RedisStreamEntry(id: entryDetails[0].AsRedisValue(), + values: ParseStreamEntryValues(entryDetails[1])); }); } @@ -1715,7 +1710,6 @@ protected NameValueEntry[] ParseStreamEntryValues(RawResult result) // 3) "temperature" // 4) "18.2" - if (result.Type != ResultType.MultiBulk) { return null; diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs b/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs index b5a00ea08..32ab068dc 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs @@ -36,8 +36,6 @@ public static class StreamConstants /// public static readonly RedisValue UndeliveredMessages = ">"; - internal static readonly RedisValue Blank = ""; - internal static readonly RedisValue Consumers = "CONSUMERS"; internal static readonly RedisValue Count = "COUNT"; diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs b/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs index 50530450b..f37e4be5b 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamConsumer.cs @@ -6,7 +6,7 @@ namespace StackExchange.Redis /// public struct StreamConsumer { - internal StreamConsumer(RedisValue name, RedisValue pendingMessageCount) + internal StreamConsumer(RedisValue name, int pendingMessageCount) { Name = name; PendingMessageCount = pendingMessageCount; @@ -20,6 +20,6 @@ internal StreamConsumer(RedisValue name, RedisValue pendingMessageCount) /// /// The number of messages that have been delivered by not yet acknowledged by the consumer. /// - public RedisValue PendingMessageCount { get; } + public int PendingMessageCount { get; } } } diff --git a/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs index eff934606..61819b910 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamPendingInfo.cs @@ -6,7 +6,7 @@ namespace StackExchange.Redis /// public struct StreamPendingInfo { - internal StreamPendingInfo(RedisValue pendingMessageCount, + internal StreamPendingInfo(int pendingMessageCount, RedisValue lowestId, RedisValue highestId, StreamConsumer[] consumers) @@ -20,7 +20,7 @@ internal StreamPendingInfo(RedisValue pendingMessageCount, /// /// The number of pending messages. A pending message is a message that has been consumed but not yet acknowledged. /// - public RedisValue PendingMessageCount { get; } + public int PendingMessageCount { get; } /// /// The lowest message ID in the set of pending messages. diff --git a/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs index 855db5b6f..180ad6326 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamPendingMessageInfo.cs @@ -9,8 +9,8 @@ public struct StreamPendingMessageInfo { internal StreamPendingMessageInfo(RedisValue messageId, RedisValue consumerName, - RedisValue idleTimeInMs, - RedisValue deliveryCount) + long idleTimeInMs, + int deliveryCount) { MessageId = messageId; ConsumerName = consumerName; @@ -31,11 +31,11 @@ internal StreamPendingMessageInfo(RedisValue messageId, /// /// The time that has passed since the message was last delivered to a consumer. /// - public RedisValue IdleTimeInMilliseconds { get; } + public long IdleTimeInMilliseconds { get; } /// /// The number of times the message has been delivered to a consumer. /// - public RedisValue DeliveryCount { get; } + public int DeliveryCount { get; } } } From 42e108646f2100ff68401297de60593bc7e55a49 Mon Sep 17 00:00:00 2001 From: ttingen Date: Sat, 30 Jun 2018 13:09:23 -0400 Subject: [PATCH 09/15] Renamed the "InfoGet" methods. --- .../DatabaseWrapperTests.cs | 20 ++++++++-------- StackExchange.Redis.Tests/Streams.cs | 24 +++++++++---------- StackExchange.Redis.Tests/WrapperBaseTests.cs | 20 ++++++++-------- .../Redis/Interfaces/IDatabase.cs | 16 ++++++------- .../Redis/Interfaces/IDatabaseAsync.cs | 10 ++++---- .../KeyspaceIsolation/DatabaseWrapper.cs | 20 ++++++++-------- .../Redis/KeyspaceIsolation/WrapperBase.cs | 20 ++++++++-------- .../StackExchange/Redis/RedisDatabase.cs | 20 ++++++++-------- .../StackExchange/Redis/StreamConsumerInfo.cs | 2 +- .../StackExchange/Redis/StreamGroupInfo.cs | 2 +- .../StackExchange/Redis/StreamInfo.cs | 2 +- 11 files changed, 78 insertions(+), 78 deletions(-) diff --git a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs index d13ae01dd..3b5b6aa1d 100644 --- a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs +++ b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs @@ -846,8 +846,8 @@ public void StreamClaimMessagesReturningIds() [Fact] public void StreamConsumerInfoGet() { - wrapper.StreamConsumerInfoGet("key", "group", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamConsumerInfoGet("prefix:key", "group", CommandFlags.HighPriority)); + wrapper.StreamConsumerInfo("key", "group", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamConsumerInfo("prefix:key", "group", CommandFlags.HighPriority)); } [Fact] @@ -860,15 +860,15 @@ public void StreamCreateConsumerGroup() [Fact] public void StreamGroupInfoGet() { - wrapper.StreamGroupInfoGet("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamGroupInfoGet("prefix:key", CommandFlags.HighPriority)); + wrapper.StreamGroupInfo("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamGroupInfo("prefix:key", CommandFlags.HighPriority)); } [Fact] public void StreamInfoGet() { - wrapper.StreamInfoGet("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamInfoGet("prefix:key", CommandFlags.HighPriority)); + wrapper.StreamInfo("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamInfo("prefix:key", CommandFlags.HighPriority)); } [Fact] @@ -889,15 +889,15 @@ public void StreamMessagesDelete() [Fact] public void StreamPendingInfoGet() { - wrapper.StreamPendingInfoGet("key", "group", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingInfoGet("prefix:key", "group", CommandFlags.HighPriority)); + wrapper.StreamPending("key", "group", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPending("prefix:key", "group", CommandFlags.HighPriority)); } [Fact] public void StreamPendingMessageInfoGet() { - wrapper.StreamPendingMessageInfoGet("key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingMessageInfoGet("prefix:key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority)); + wrapper.StreamPendingMessages("key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessages("prefix:key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority)); } [Fact] diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index 02402f7fa..c7ae94a6c 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -272,7 +272,7 @@ public void StreamConsumerGroupClaimMessages() // Claim the 3 messages consumed by consumer2 for consumer1. // Get the pending messages for consumer2. - var pendingMessages = db.StreamPendingMessageInfoGet(key, groupName, + var pendingMessages = db.StreamPendingMessages(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 10, @@ -286,7 +286,7 @@ public void StreamConsumerGroupClaimMessages() messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray()); // Now see how many messages are pending for each consumer - var pendingSummary = db.StreamPendingInfoGet(key, groupName); + var pendingSummary = db.StreamPending(key, groupName); Assert.NotNull(pendingSummary.Consumers); Assert.True(pendingSummary.Consumers.Length == 1); @@ -324,7 +324,7 @@ public void StreamConsumerGroupClaimMessagesReturningIds() // Claim the 3 messages consumed by consumer2 for consumer1. // Get the pending messages for consumer2. - var pendingMessages = db.StreamPendingMessageInfoGet(key, groupName, + var pendingMessages = db.StreamPendingMessages(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 10, @@ -362,7 +362,7 @@ public void StreamConsumerGroupViewPendingInfoNoConsumers() db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); - var pendingInfo = db.StreamPendingInfoGet(key, groupName); + var pendingInfo = db.StreamPending(key, groupName); Assert.Equal(0, pendingInfo.PendingMessageCount); Assert.True(pendingInfo.LowestPendingMessageId == RedisValue.Null); @@ -388,7 +388,7 @@ public void StreamConsumerGroupViewPendingInfoWhenNothingPending() db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); - var pendingMessages = db.StreamPendingMessageInfoGet(key, + var pendingMessages = db.StreamPendingMessages(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, @@ -427,7 +427,7 @@ public void StreamConsumerGroupViewPendingInfoSummary() // Read the remaining messages into the second consumer. var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); - var pendingInfo = db.StreamPendingInfoGet(key, groupName); + var pendingInfo = db.StreamPending(key, groupName); Assert.Equal(4, pendingInfo.PendingMessageCount); Assert.Equal(id1, pendingInfo.LowestPendingMessageId); @@ -470,7 +470,7 @@ public void StreamConsumerGroupViewPendingMessageInfo() var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); // Get the pending info about the messages themselves. - var pendingMessageInfoList = db.StreamPendingMessageInfoGet(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 10, RedisValue.Null); + var pendingMessageInfoList = db.StreamPendingMessages(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 10, RedisValue.Null); Assert.NotNull(pendingMessageInfoList); Assert.Equal(4, pendingMessageInfoList.Length); @@ -509,7 +509,7 @@ public void StreamConsumerGroupViewPendingMessageInfoForConsumer() var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); // Get the pending info about the messages themselves. - var pendingMessageInfoList = db.StreamPendingMessageInfoGet(key, + var pendingMessageInfoList = db.StreamPendingMessages(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, @@ -598,7 +598,7 @@ public void StreamGroupInfoGet() // Read the remaining messages into the second consumer. var consumer2Messages = db.StreamReadGroup(key, group2, consumer2, StreamConstants.UndeliveredMessages); - var groupInfoList = db.StreamGroupInfoGet(key); + var groupInfoList = db.StreamGroupInfo(key); Assert.NotNull(groupInfoList); Assert.Equal(2, groupInfoList.Length); @@ -634,7 +634,7 @@ public void StreamGroupConsumerInfoGet() db.StreamReadGroup(key, group, consumer1, StreamConstants.UndeliveredMessages, 1); db.StreamReadGroup(key, group, consumer2, StreamConstants.UndeliveredMessages); - var consumerInfoList = db.StreamConsumerInfoGet(key, group); + var consumerInfoList = db.StreamConsumerInfo(key, group); Assert.NotNull(consumerInfoList); Assert.Equal(2, consumerInfoList.Length); @@ -663,7 +663,7 @@ public void StreamInfoGet() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - var streamInfo = db.StreamInfoGet(key); + var streamInfo = db.StreamInfo(key); Assert.Equal(4, streamInfo.Length); Assert.True(streamInfo.RadixTreeKeys > 0); @@ -693,7 +693,7 @@ public void StreamInfoGetWithEmptyStream() Assert.Equal(0, db.StreamLength(key)); - var streamInfo = db.StreamInfoGet(key); + var streamInfo = db.StreamInfo(key); Assert.True(streamInfo.FirstEntry.IsNull); Assert.True(streamInfo.LastEntry.IsNull); diff --git a/StackExchange.Redis.Tests/WrapperBaseTests.cs b/StackExchange.Redis.Tests/WrapperBaseTests.cs index c052c44a5..cb8053157 100644 --- a/StackExchange.Redis.Tests/WrapperBaseTests.cs +++ b/StackExchange.Redis.Tests/WrapperBaseTests.cs @@ -804,8 +804,8 @@ public void StreamClaimMessagesReturningIdsAsync() [Fact] public void StreamConsumerInfoGetAsync() { - wrapper.StreamConsumerInfoGetAsync("key", "group", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamConsumerInfoGetAsync("prefix:key", "group", CommandFlags.HighPriority)); + wrapper.StreamConsumerInfoAsync("key", "group", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamConsumerInfoAsync("prefix:key", "group", CommandFlags.HighPriority)); } [Fact] @@ -818,15 +818,15 @@ public void StreamCreateConsumerGroupAsync() [Fact] public void StreamGroupInfoGetAsync() { - wrapper.StreamGroupInfoGetAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamGroupInfoGetAsync("prefix:key", CommandFlags.HighPriority)); + wrapper.StreamGroupInfoAsync("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamGroupInfoAsync("prefix:key", CommandFlags.HighPriority)); } [Fact] public void StreamInfoGetAsync() { - wrapper.StreamInfoGetAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamInfoGetAsync("prefix:key", CommandFlags.HighPriority)); + wrapper.StreamInfoAsync("key", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamInfoAsync("prefix:key", CommandFlags.HighPriority)); } [Fact] @@ -847,15 +847,15 @@ public void StreamMessagesDeleteAsync() [Fact] public void StreamPendingInfoGetAsync() { - wrapper.StreamPendingInfoGetAsync("key", "group", CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingInfoGetAsync("prefix:key", "group", CommandFlags.HighPriority)); + wrapper.StreamPendingAsync("key", "group", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingAsync("prefix:key", "group", CommandFlags.HighPriority)); } [Fact] public void StreamPendingMessageInfoGetAsync() { - wrapper.StreamPendingMessageInfoGetAsync("key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingMessageInfoGetAsync("prefix:key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority)); + wrapper.StreamPendingMessagesAsync("key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessagesAsync("prefix:key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority)); } [Fact] diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs index 078ae26c5..5da457e61 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs @@ -1505,18 +1505,18 @@ IEnumerable SortedSetScan(RedisKey key, /// /// The key of the stream. /// The flags to use for this operation. - /// A instance with information about the stream. + /// A instance with information about the stream. /// https://redis.io/topics/streams-intro - StreamInfo StreamInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None); + StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None); /// /// Retrieve information about the groups created for the given stream. This is the equivalent of calling "XINFO GROUPS key". /// /// The key of the stream. /// The flags to use for this operation. - /// An instance of for each of the stream's groups. + /// An instance of for each of the stream's groups. /// https://redis.io/topics/streams-intro - StreamGroupInfo[] StreamGroupInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None); + StreamGroupInfo[] StreamGroupInfo(RedisKey key, CommandFlags flags = CommandFlags.None); /// /// Retrieve information about the consumers for the given consumer group. This is the equivalent of calling "XINFO GROUPS key group". @@ -1524,9 +1524,9 @@ IEnumerable SortedSetScan(RedisKey key, /// The key of the stream. /// The consumer group name. /// The flags to use for this operation. - /// An instance of for each of the consumer group's consumers. + /// An instance of for each of the consumer group's consumers. /// https://redis.io/topics/streams-intro - StreamConsumerInfo[] StreamConsumerInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); /// /// Return the number of entries in a stream. @@ -1556,7 +1556,7 @@ IEnumerable SortedSetScan(RedisKey key, /// An instance of . contains the number of pending messages, the highest and lowest ID of the pending messages, and the consumers with their pending message count. /// The equivalent of calling XPENDING key group. /// https://redis.io/commands/xpending - StreamPendingInfo StreamPendingInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); /// /// View information about each pending message. @@ -1571,7 +1571,7 @@ IEnumerable SortedSetScan(RedisKey key, /// An instance of for each pending message. /// Equivalent of calling XPENDING key group start-id end-id count consumer-name. /// https://redis.io/commands/xpending - StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); + StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); /// /// Read a stream using the given range of IDs. diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs index 0d58a51b7..c57e64f99 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs @@ -1417,7 +1417,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// A instance with information about the stream. /// https://redis.io/topics/streams-intro - Task StreamInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None); /// /// Retrieve information about the groups created for the given stream. This is the equivalent of calling "XINFO GROUPS key". @@ -1426,7 +1426,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// An instance of for each of the stream's groups. /// https://redis.io/topics/streams-intro - Task StreamGroupInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + Task StreamGroupInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None); /// /// Retrieve information about the consumers for the given consumer group. This is the equivalent of calling "XINFO GROUPS key group". @@ -1436,7 +1436,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// An instance of for each of the consumer group's consumers. /// https://redis.io/topics/streams-intro - Task StreamConsumerInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + Task StreamConsumerInfoAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); /// /// Return the number of entries in a stream. @@ -1466,7 +1466,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// An instance of . contains the number of pending messages, the highest and lowest ID of the pending messages, and the consumers with their pending message count. /// The equivalent of calling XPENDING key group. /// https://redis.io/commands/xpending - Task StreamPendingInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + Task StreamPendingAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); /// /// View information about each pending message. @@ -1481,7 +1481,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// An instance of for each pending message. /// Equivalent of calling XPENDING key group start-id end-id count consumer-name. /// https://redis.io/commands/xpending - Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); + Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); /// /// Read a stream using the given range of IDs. diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs index 863877eff..db2749303 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -632,19 +632,19 @@ public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisV return Inner.StreamCreateConsumerGroup(ToInner(key), groupName, readFrom, flags); } - public StreamInfo StreamInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None) + public StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None) { - return Inner.StreamInfoGet(ToInner(key), flags); + return Inner.StreamInfo(ToInner(key), flags); } - public StreamGroupInfo[] StreamGroupInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None) + public StreamGroupInfo[] StreamGroupInfo(RedisKey key, CommandFlags flags = CommandFlags.None) { - return Inner.StreamGroupInfoGet(ToInner(key), flags); + return Inner.StreamGroupInfo(ToInner(key), flags); } - public StreamConsumerInfo[] StreamConsumerInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + public StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { - return Inner.StreamConsumerInfoGet(ToInner(key), groupName, flags); + return Inner.StreamConsumerInfo(ToInner(key), groupName, flags); } public long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None) @@ -657,14 +657,14 @@ public long StreamMessagesDelete(RedisKey key, RedisValue[] messageIds, CommandF return Inner.StreamMessagesDelete(ToInner(key), messageIds, flags); } - public StreamPendingInfo StreamPendingInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + public StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { - return Inner.StreamPendingInfoGet(ToInner(key), groupName, flags); + return Inner.StreamPending(ToInner(key), groupName, flags); } - public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) { - return Inner.StreamPendingMessageInfoGet(ToInner(key), groupName, minId, maxId, count, consumerName, flags); + return Inner.StreamPendingMessages(ToInner(key), groupName, minId, maxId, count, consumerName, flags); } public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs index f14112ab8..4f989554c 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs @@ -611,19 +611,19 @@ public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupN return Inner.StreamCreateConsumerGroupAsync(ToInner(key), groupName, readFrom, flags); } - public Task StreamInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + public Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - return Inner.StreamInfoGetAsync(ToInner(key), flags); + return Inner.StreamInfoAsync(ToInner(key), flags); } - public Task StreamGroupInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + public Task StreamGroupInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - return Inner.StreamGroupInfoGetAsync(ToInner(key), flags); + return Inner.StreamGroupInfoAsync(ToInner(key), flags); } - public Task StreamConsumerInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + public Task StreamConsumerInfoAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { - return Inner.StreamConsumerInfoGetAsync(ToInner(key), groupName, flags); + return Inner.StreamConsumerInfoAsync(ToInner(key), groupName, flags); } public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) @@ -636,14 +636,14 @@ public Task StreamMessagesDeleteAsync(RedisKey key, RedisValue[] messageId return Inner.StreamMessagesDeleteAsync(ToInner(key), messageIds, flags); } - public Task StreamPendingInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + public Task StreamPendingAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { - return Inner.StreamPendingInfoGetAsync(ToInner(key), groupName, flags); + return Inner.StreamPendingAsync(ToInner(key), groupName, flags); } - public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) { - return Inner.StreamPendingMessageInfoGetAsync(ToInner(key), groupName, minId, maxId, count, consumerName, flags); + return Inner.StreamPendingMessagesAsync(ToInner(key), groupName, minId, maxId, count, consumerName, flags); } public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index 5f74b43ec..e027b94a3 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -1779,7 +1779,7 @@ public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupN return ExecuteAsync(msg, ResultProcessor.Boolean); } - public StreamConsumerInfo[] StreamConsumerInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + public StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, @@ -1794,7 +1794,7 @@ public StreamConsumerInfo[] StreamConsumerInfoGet(RedisKey key, RedisValue group return ExecuteSync(msg, ResultProcessor.StreamConsumerInfo); } - public Task StreamConsumerInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + public Task StreamConsumerInfoAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, @@ -1809,25 +1809,25 @@ public Task StreamConsumerInfoGetAsync(RedisKey key, Redis return ExecuteAsync(msg, ResultProcessor.StreamConsumerInfo); } - public StreamGroupInfo[] StreamGroupInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None) + public StreamGroupInfo[] StreamGroupInfo(RedisKey key, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key); return ExecuteSync(msg, ResultProcessor.StreamGroupInfo); } - public Task StreamGroupInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + public Task StreamGroupInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key); return ExecuteAsync(msg, ResultProcessor.StreamGroupInfo); } - public StreamInfo StreamInfoGet(RedisKey key, CommandFlags flags = CommandFlags.None) + public StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key); return ExecuteSync(msg, ResultProcessor.StreamInfo); } - public Task StreamInfoGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + public Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key); return ExecuteAsync(msg, ResultProcessor.StreamInfo); @@ -1867,25 +1867,25 @@ public Task StreamMessagesDeleteAsync(RedisKey key, RedisValue[] messageId return ExecuteAsync(msg, ResultProcessor.Int64); } - public StreamPendingInfo StreamPendingInfoGet(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + public StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName); return ExecuteSync(msg, ResultProcessor.StreamPendingInfo); } - public Task StreamPendingInfoGetAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + public Task StreamPendingAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName); return ExecuteAsync(msg, ResultProcessor.StreamPendingInfo); } - public StreamPendingMessageInfo[] StreamPendingMessageInfoGet(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) { var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, consumerName, flags); return ExecuteSync(msg, ResultProcessor.StreamPendingMessages); } - public Task StreamPendingMessageInfoGetAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) { var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, consumerName, flags); return ExecuteAsync(msg, ResultProcessor.StreamPendingMessages); diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs index 27328abde..d7b6adc63 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamConsumerInfo.cs @@ -2,7 +2,7 @@ namespace StackExchange.Redis { /// - /// Describes a consumer within a consumer group, retrieved using the XINFO CONSUMERS command. + /// Describes a consumer within a consumer group, retrieved using the XINFO CONSUMERS command. /// public struct StreamConsumerInfo { diff --git a/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs index cafc3484f..ae120a5e4 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamGroupInfo.cs @@ -2,7 +2,7 @@ namespace StackExchange.Redis { /// - /// Describes a consumer group retrieved using the XINFO GROUPS command. + /// Describes a consumer group retrieved using the XINFO GROUPS command. /// public struct StreamGroupInfo { diff --git a/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs b/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs index 497572853..3cc2d7337 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamInfo.cs @@ -2,7 +2,7 @@ namespace StackExchange.Redis { /// - /// Describes stream information retrieved using the XINFO STREAM command. + /// Describes stream information retrieved using the XINFO STREAM command. /// public struct StreamInfo { From 93e495ed42cef519571df6f07d9e04446259601b Mon Sep 17 00:00:00 2001 From: ttingen Date: Sun, 1 Jul 2018 14:55:31 -0400 Subject: [PATCH 10/15] Added RedisType.Stream and a corresponding unit test. Also some additional cleanup. --- StackExchange.Redis.Tests/Streams.cs | 18 ++++++++++ .../StackExchange/Redis/Enums/RedisType.cs | 7 ++++ .../StackExchange/Redis/RedisDatabase.cs | 36 +++++++++---------- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index c7ae94a6c..8aaeab97a 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -10,6 +10,24 @@ public class Streams : TestBase { public Streams(ITestOutputHelper output) : base(output) { } + [Fact] + public void IsStreamType() + { + using (var conn = Create()) + { + var key = GetUniqueKey("type_check"); + + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + db.StreamAdd(key, "field1", "value1"); + + var keyType = db.KeyType(key); + + Assert.Equal(RedisType.Stream, keyType); + } + } + [Fact] public void StreamAddSinglePairWithAutoId() { diff --git a/StackExchange.Redis/StackExchange/Redis/Enums/RedisType.cs b/StackExchange.Redis/StackExchange/Redis/Enums/RedisType.cs index 240187c10..54d49cd03 100644 --- a/StackExchange.Redis/StackExchange/Redis/Enums/RedisType.cs +++ b/StackExchange.Redis/StackExchange/Redis/Enums/RedisType.cs @@ -38,6 +38,13 @@ public enum RedisType /// https://redis.io/commands#hash Hash, /// + /// A Redis Stream is a data structure which models the behavior of an append only log but it has more + /// advanced features for manipulating the data contained within the stream. Each entry in a + /// stream contains a unique message ID and a list of name/value pairs containing the entry's data. + /// + /// https://redis.io/commands#stream + Stream, + /// /// The data-type was not recognised by the client library /// Unknown, diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index e027b94a3..8d4489aed 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -2353,7 +2353,7 @@ private Message GetMultiStreamReadMessage(IList Date: Sun, 1 Jul 2018 17:00:04 -0400 Subject: [PATCH 11/15] Added tests to validate responses when there are no Stream Groups or Consumers. --- StackExchange.Redis.Tests/Streams.cs | 72 ++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index 8aaeab97a..3984ae94d 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -116,6 +116,31 @@ public void StreamAddMultipleValuePairsWithManualId() } } + [Fact] + public void StreamConsumerGroupWithNoConsumers() + { + var key = GetUniqueKey("group_with_no_consumers"); + var groupName = "test_group"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + // Create a stream + db.StreamAdd(key, "field1", "value1"); + + // Create a group + var result = db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + + // Query redis for the group consumers, expect an empty list in response. + var consumers = db.StreamConsumerInfo(key, groupName); + + Assert.True(consumers.Length == 0); + } + } + [Fact] public void StreamCreateConsumerGroup() { @@ -718,6 +743,53 @@ public void StreamInfoGetWithEmptyStream() } } + [Fact] + public void StreamNoConsumerGroups() + { + var key = GetUniqueKey("stream_with_no_consumers"); + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id = db.StreamAdd(key, "field1", "value1"); + + var groups = db.StreamGroupInfo(key); + + Assert.NotNull(groups); + Assert.True(groups.Length == 0); + } + } + + [Fact] + public void StreamPendingNoMessagesOrConsumers() + { + var key = GetUniqueKey("stream_pending_empty"); + var groupName = "test_group"; + + using (var conn = Create()) + { + Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); + + var db = conn.GetDatabase(); + + var id = db.StreamAdd(key, "field1", "value1"); + db.StreamMessagesDelete(key, new RedisValue[1] { id }); + + db.StreamCreateConsumerGroup(key, groupName, "0-0"); + + var pendingInfo = db.StreamPending(key, "test_group"); + + Assert.Equal(0, pendingInfo.PendingMessageCount); + Assert.Equal(RedisValue.Null, pendingInfo.LowestPendingMessageId); + Assert.Equal(RedisValue.Null, pendingInfo.HighestPendingMessageId); + Assert.NotNull(pendingInfo.Consumers); + Assert.True(pendingInfo.Consumers.Length == 0); + } + } + [Fact] public void StreamRead() { From f319d28622473585d650ba7a129d6e45028c57b1 Mon Sep 17 00:00:00 2001 From: ttingen Date: Sun, 1 Jul 2018 18:10:55 -0400 Subject: [PATCH 12/15] More overload consolidation and updates. - Removed 4 StreamAdd overloads. - Renamed streamEntryId to messageId and defaulted it to "*" (auto-generated ID). - Added a StreamIdPair struct and updated one of the StreamRead overloads to use it. --- .../DatabaseWrapperTests.cs | 25 ++---- StackExchange.Redis.Tests/Streams.cs | 41 ++++----- StackExchange.Redis.Tests/WrapperBaseTests.cs | 25 ++---- .../Redis/Interfaces/IDatabase.cs | 41 ++------- .../Redis/Interfaces/IDatabaseAsync.cs | 41 ++------- .../KeyspaceIsolation/DatabaseWrapper.cs | 22 ++--- .../Redis/KeyspaceIsolation/WrapperBase.cs | 22 ++--- .../StackExchange/Redis/RedisDatabase.cs | 90 +++++-------------- .../StackExchange/Redis/StreamIdPair.cs | 37 ++++++++ 9 files changed, 117 insertions(+), 227 deletions(-) create mode 100644 StackExchange.Redis/StackExchange/Redis/StreamIdPair.cs diff --git a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs index 3b5b6aa1d..add2b6533 100644 --- a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs +++ b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs @@ -800,31 +800,16 @@ public void StreamAcknowledge_2() [Fact] public void StreamAdd_1() { - var fields = new NameValueEntry[0]; - wrapper.StreamAdd("key", fields, 1000, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamAdd("prefix:key", fields, 1000, true, CommandFlags.HighPriority)); + wrapper.StreamAdd("key", "field1", "value1", "*", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAdd("prefix:key", "field1", "value1", "*", 1000, true, CommandFlags.HighPriority)); } [Fact] public void StreamAdd_2() { var fields = new NameValueEntry[0]; - wrapper.StreamAdd("key", "0-0", fields, 1000, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamAdd("prefix:key", "0-0", fields, 1000, true, CommandFlags.HighPriority)); - } - - [Fact] - public void StreamAdd_3() - { - wrapper.StreamAdd("key", "field1", "value1", 1000, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamAdd("prefix:key", "field1", "value1", 1000, true, CommandFlags.HighPriority)); - } - - [Fact] - public void StreamAdd_4() - { - wrapper.StreamAdd("key", "0-0", "field1", "value1", 1000, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamAdd("prefix:key", "0-0", "field1", "value1", 1000, true, CommandFlags.HighPriority)); + wrapper.StreamAdd("key", fields, "*", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAdd("prefix:key", fields, "*", 1000, true, CommandFlags.HighPriority)); } [Fact] @@ -910,7 +895,7 @@ public void StreamRange() [Fact] public void StreamRead_1() { - var keysAndIds = new KeyValuePair[0] { }; + var keysAndIds = new StreamIdPair[0] { }; wrapper.StreamRead(keysAndIds, null, CommandFlags.HighPriority); mock.Verify(_ => _.StreamRead(keysAndIds, null, CommandFlags.HighPriority)); } diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index 3984ae94d..f72e7ba02 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -75,14 +75,15 @@ public void StreamAddMultipleValuePairsWithAutoId() [Fact] public void StreamAddWithManualId() { - var id = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}-0"; + var id = "42-0"; + var key = GetUniqueKey("manual_id"); using (var conn = Create()) { Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); var db = conn.GetDatabase(); - var messageId = db.StreamAdd(GetUniqueKey("manual_id"), id, "field1", "value1"); + var messageId = db.StreamAdd(key, "field1", "value1", id); Assert.Equal(id, messageId); } @@ -91,7 +92,7 @@ public void StreamAddWithManualId() [Fact] public void StreamAddMultipleValuePairsWithManualId() { - var id = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}-0"; + var id = "42-0"; var key = GetUniqueKey("manual_id_multiple_values"); using (var conn = Create()) @@ -106,7 +107,7 @@ public void StreamAddMultipleValuePairsWithManualId() new NameValueEntry("field2", "value2") }; - var messageId = db.StreamAdd(key, id, fields); + var messageId = db.StreamAdd(key, fields, id); var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue); Assert.Equal(id, messageId); @@ -885,10 +886,10 @@ public void StreamReadExpectedExceptionInvalidCountMultipleStream() { Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); - var streamPairs = new List> + var streamPairs = new StreamIdPair[] { - new KeyValuePair("key1", "0-0"), - new KeyValuePair("key2", "0-0") + new StreamIdPair("key1", "0-0"), + new StreamIdPair("key2", "0-0") }; @@ -932,7 +933,7 @@ public void StreamReadExpectedExceptionEmptyStreamList() var db = conn.GetDatabase(); - var emptyList = new KeyValuePair[0]; + var emptyList = new StreamIdPair[0]; Assert.Throws(() => db.StreamRead(emptyList)); } @@ -956,10 +957,10 @@ public void StreamReadMultipleStreams() var id4 = db.StreamAdd(key2, "field4", "value4"); // Read from both streams at the same time. - var streamList = new KeyValuePair[2] + var streamList = new StreamIdPair[2] { - new KeyValuePair(key1, "0-0"), - new KeyValuePair(key2, "0-0") + new StreamIdPair(key1, "0-0"), + new StreamIdPair(key2, "0-0") }; var streams = db.StreamRead(streamList); @@ -995,10 +996,10 @@ public void StreamReadMultipleStreamsWithCount() var id3 = db.StreamAdd(key2, "field3", "value3"); var id4 = db.StreamAdd(key2, "field4", "value4"); - var streamList = new KeyValuePair[2] + var streamList = new StreamIdPair[2] { - new KeyValuePair(key1, "0-0"), - new KeyValuePair(key2, "0-0") + new StreamIdPair(key1, "0-0"), + new StreamIdPair(key2, "0-0") }; var streams = db.StreamRead(streamList, countPerStream: 1); @@ -1033,12 +1034,12 @@ public void StreamReadMultipleStreamsWithReadPastSecondStream() var id3 = db.StreamAdd(key2, "field3", "value3"); var id4 = db.StreamAdd(key2, "field4", "value4"); - var streamList = new KeyValuePair[2] + var streamList = new StreamIdPair[2] { - new KeyValuePair(key1, "0-0"), + new StreamIdPair(key1, "0-0"), // read past the end of stream # 2 - new KeyValuePair(key2, id4) + new StreamIdPair(key2, id4) }; var streams = db.StreamRead(streamList); @@ -1068,11 +1069,11 @@ public void StreamReadMultipleStreamsWithEmptyResponse() var id3 = db.StreamAdd(key2, "field3", "value3"); var id4 = db.StreamAdd(key2, "field4", "value4"); - var streamList = new KeyValuePair[2] + var streamList = new StreamIdPair[2] { // Read past the end of both streams. - new KeyValuePair(key1, id2), - new KeyValuePair(key2, id4) + new StreamIdPair(key1, id2), + new StreamIdPair(key2, id4) }; var streams = db.StreamRead(streamList); diff --git a/StackExchange.Redis.Tests/WrapperBaseTests.cs b/StackExchange.Redis.Tests/WrapperBaseTests.cs index cb8053157..c1e873f18 100644 --- a/StackExchange.Redis.Tests/WrapperBaseTests.cs +++ b/StackExchange.Redis.Tests/WrapperBaseTests.cs @@ -758,31 +758,16 @@ public void StreamAcknowledgeAsync_2() [Fact] public void StreamAddAsync_1() { - var fields = new NameValueEntry[0]; - wrapper.StreamAddAsync("key", fields, 1000, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamAddAsync("prefix:key", fields, 1000, true, CommandFlags.HighPriority)); + wrapper.StreamAddAsync("key", "field1", "value1", "*", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAddAsync("prefix:key", "field1", "value1", "*", 1000, true, CommandFlags.HighPriority)); } [Fact] public void StreamAddAsync_2() { var fields = new NameValueEntry[0]; - wrapper.StreamAddAsync("key", "0-0", fields, 1000, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamAddAsync("prefix:key", "0-0", fields, 1000, true, CommandFlags.HighPriority)); - } - - [Fact] - public void StreamAddAsync_3() - { - wrapper.StreamAddAsync("key", "field1", "value1", 1000, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamAddAsync("prefix:key", "field1", "value1", 1000, true, CommandFlags.HighPriority)); - } - - [Fact] - public void StreamAddAsync_4() - { - wrapper.StreamAddAsync("key", "0-0", "field1", "value1", 1000, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamAddAsync("prefix:key", "0-0", "field1", "value1", 1000, true, CommandFlags.HighPriority)); + wrapper.StreamAddAsync("key", fields, "*", 1000, true, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamAddAsync("prefix:key", fields, "*", 1000, true, CommandFlags.HighPriority)); } [Fact] @@ -868,7 +853,7 @@ public void StreamRangeAsync() [Fact] public void StreamReadAsync_1() { - var keysAndIds = new KeyValuePair[0] { }; + var keysAndIds = new StreamIdPair[0] { }; wrapper.StreamReadAsync(keysAndIds, null, CommandFlags.HighPriority); mock.Verify(_ => _.StreamReadAsync(keysAndIds, null, CommandFlags.HighPriority)); } diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs index 5da457e61..2d261322c 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs @@ -1411,57 +1411,32 @@ IEnumerable SortedSetScan(RedisKey key, /// https://redis.io/topics/streams-intro long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); - /// - /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. - /// - /// The key of the stream. - /// The fields and their associated values to set in the stream entry. - /// The flags to use for this operation. - /// The maximum length of the stream. - /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. - /// The ID of the newly created message. - /// https://redis.io/commands/xadd - RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); - /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. /// /// The key of the stream. /// The field name for the stream entry. /// The value to set in the stream entry. - /// The flags to use for this operation. - /// The maximum length of the stream. - /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. - /// The ID of the newly created message. - /// https://redis.io/commands/xadd - RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); - - /// - /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. - /// - /// The key of the stream. - /// The ID to assign to the stream entry. This must be a unique value per entry. - /// The field name for the stream entry. - /// The value to set in the stream entry. - /// The flags to use for this operation. + /// The ID to assign to the stream entry, defaults to an auto-generated ID ("*"). /// The maximum length of the stream. /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The flags to use for this operation. /// The ID of the newly created message. /// https://redis.io/commands/xadd - RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. /// /// The key of the stream. - /// The ID to assign to the stream entry. This must be a unique value per entry. /// The fields and their associated values to set in the stream entry. - /// The flags to use for this operation. + /// The ID to assign to the stream entry, defaults to an auto-generated ID ("*"). /// The maximum length of the stream. /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The flags to use for this operation. /// The ID of the newly created message. /// https://redis.io/commands/xadd - RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). @@ -1601,13 +1576,13 @@ IEnumerable SortedSetScan(RedisKey key, /// /// Read from multiple streams. /// - /// The list of streams and the ID from which to begin reading for each stream. + /// The list of streams and the ID from which to begin reading for each stream. /// The maximum number of messages to return from each stream. /// The flags to use for this operation. /// An instance of for each stream. /// Equivalent of calling XREAD COUNT num STREAMS key1 key2 id1 id2. /// https://redis.io/commands/xread - RedisStream[] StreamRead(IList> streamAndIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None); + RedisStream[] StreamRead(StreamIdPair[] streamIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None); /// /// Read messages from a stream and an associated consumer group. diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs index c57e64f99..cc8357460 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs @@ -1321,57 +1321,32 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// https://redis.io/topics/streams-intro Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); - /// - /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. - /// - /// The key of the stream. - /// The fields and their associated values to set in the stream entry. - /// The flags to use for this operation. - /// The maximum length of the stream. - /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. - /// The ID of the newly created message. - /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); - /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. /// /// The key of the stream. /// The field name for the stream entry. /// The value to set in the stream entry. - /// The flags to use for this operation. - /// The maximum length of the stream. - /// If true, use the "~" argument to keep the stream length at or slightly above the maxLength. - /// The ID of the newly created message. - /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); - - /// - /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. - /// - /// The key of the stream. - /// The ID to assign to the stream entry. This must be a unique value per entry. - /// The field name for the stream entry. - /// The value to set in the stream entry. - /// The flags to use for this operation. + /// The ID to assign to the stream entry, defaults to an auto-generated ID ("*"). /// The maximum length of the stream. /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The flags to use for this operation. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. /// /// The key of the stream. - /// The ID to assign to the stream entry. This must be a unique value per entry. /// The fields and their associated values to set in the stream entry. - /// The flags to use for this operation. + /// The ID to assign to the stream entry, defaults to an auto-generated ID ("*"). /// The maximum length of the stream. /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The flags to use for this operation. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). @@ -1511,13 +1486,13 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// /// Read from multiple streams. /// - /// The list of streams and the ID from which to begin reading for each stream. + /// The list of streams and the ID from which to begin reading for each stream. /// The maximum number of messages to return from each stream. /// The flags to use for this operation. /// An instance of for each stream. /// Equivalent of calling XREAD COUNT num STREAMS key1 key2 id1 id2. /// https://redis.io/commands/xread - Task StreamReadAsync(IList> streamAndIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None); + Task StreamReadAsync(StreamIdPair[] streamIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None); /// /// Read messages from a stream and an associated consumer group. diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs index db2749303..506bbb91a 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -597,24 +597,14 @@ public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] m return Inner.StreamAcknowledge(ToInner(key), groupName, messageIds, flags); } - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - return Inner.StreamAdd(ToInner(key), streamPairs, maxLength, useApproximateMaxLength, flags); + return Inner.StreamAdd(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, flags); } - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - return Inner.StreamAdd(ToInner(key), streamField, streamValue, maxLength, useApproximateMaxLength, flags); - } - - public RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamAdd(ToInner(key), streamEntryId, streamField, streamValue, maxLength, useApproximateMaxLength, flags); - } - - public RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamAdd(ToInner(key), streamEntryId, streamPairs, maxLength, useApproximateMaxLength, flags); + return Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); } public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) @@ -677,9 +667,9 @@ public RedisStreamEntry[] StreamRead(RedisKey key, RedisValue afterId, int? coun return Inner.StreamRead(ToInner(key), afterId, count, flags); } - public RedisStream[] StreamRead(IList> streamWithAfterIdList, int? countPerStream = null, CommandFlags flags = CommandFlags.None) + public RedisStream[] StreamRead(StreamIdPair[] streamIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { - return Inner.StreamRead(streamWithAfterIdList, countPerStream, flags); + return Inner.StreamRead(streamIdPairs, countPerStream, flags); } public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs index 4f989554c..19d45cab2 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs @@ -576,24 +576,14 @@ public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, Red return Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageIds, flags); } - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - return Inner.StreamAddAsync(ToInner(key), streamPairs, maxLength, useApproximateMaxLength, flags); + return Inner.StreamAddAsync(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, flags); } - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { - return Inner.StreamAddAsync(ToInner(key), streamField, streamValue, maxLength, useApproximateMaxLength, flags); - } - - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamAddAsync(ToInner(key), streamEntryId, streamField, streamValue, maxLength, useApproximateMaxLength, flags); - } - - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) - { - return Inner.StreamAddAsync(ToInner(key), streamEntryId, streamPairs, maxLength, useApproximateMaxLength, flags); + return Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); } public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) @@ -656,9 +646,9 @@ public Task StreamReadAsync(RedisKey key, RedisValue afterId return Inner.StreamReadAsync(ToInner(key), afterId, count, flags); } - public Task StreamReadAsync(IList> streamWithAfterIdList, int? countPerStream = null, CommandFlags flags = CommandFlags.None) + public Task StreamReadAsync(StreamIdPair[] streamIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { - return Inner.StreamReadAsync(streamWithAfterIdList, countPerStream, flags); + return Inner.StreamReadAsync(streamIdPairs, countPerStream, flags); } public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index 8d4489aed..14851384f 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -1603,34 +1603,10 @@ public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, Red return ExecuteAsync(msg, ResultProcessor.Int64); } - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - StreamConstants.AutoGeneratedId, - maxLength, - useApproximateMaxLength, - streamPairs, - flags); - - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) - { - var msg = GetStreamAddMessage(key, - StreamConstants.AutoGeneratedId, - maxLength, - useApproximateMaxLength, - streamPairs, - flags); - - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) - { - var msg = GetStreamAddMessage(key, - StreamConstants.AutoGeneratedId, + messageId, maxLength, useApproximateMaxLength, new NameValueEntry(streamField, streamValue), @@ -1639,10 +1615,10 @@ public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue str return ExecuteSync(msg, ResultProcessor.RedisValue); } - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - StreamConstants.AutoGeneratedId, + messageId, maxLength, useApproximateMaxLength, new NameValueEntry(streamField, streamValue), @@ -1651,34 +1627,10 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamField, Red return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - streamEntryId, - maxLength, - useApproximateMaxLength, - new NameValueEntry(streamField, streamValue), - flags); - - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, RedisValue streamField, RedisValue streamValue, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) - { - var msg = GetStreamAddMessage(key, - streamEntryId, - maxLength, - useApproximateMaxLength, - new NameValueEntry(streamField, streamValue), - flags); - - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) - { - var msg = GetStreamAddMessage(key, - streamEntryId, + messageId, maxLength, useApproximateMaxLength, streamPairs, @@ -1687,10 +1639,10 @@ public RedisValue StreamAdd(RedisKey key, RedisValue streamEntryId, NameValueEnt return ExecuteSync(msg, ResultProcessor.RedisValue); } - public Task StreamAddAsync(RedisKey key, RedisValue streamEntryId, NameValueEntry[] streamPairs, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - streamEntryId, + messageId, maxLength, useApproximateMaxLength, streamPairs, @@ -1915,15 +1867,15 @@ public Task StreamReadAsync(RedisKey key, RedisValue afterId return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip); } - public RedisStream[] StreamRead(IList> streamAndIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None) + public RedisStream[] StreamRead(StreamIdPair[] streamIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { - var msg = GetMultiStreamReadMessage(streamAndIdPairs, countPerStream, flags); + var msg = GetMultiStreamReadMessage(streamIdPairs, countPerStream, flags); return ExecuteSync(msg, ResultProcessor.MultiStream); } - public Task StreamReadAsync(IList> streamAndIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None) + public Task StreamReadAsync(StreamIdPair[] streamIdPairs, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { - var msg = GetMultiStreamReadMessage(streamAndIdPairs, countPerStream, flags); + var msg = GetMultiStreamReadMessage(streamIdPairs, countPerStream, flags); return ExecuteAsync(msg, ResultProcessor.MultiStream); } @@ -2308,12 +2260,12 @@ private RedisValue GetLexRange(RedisValue value, Exclude exclude, bool isStart) return result; } - private Message GetMultiStreamReadMessage(IList> streamAndIdPairs, int? countPerStream, CommandFlags flags) + private Message GetMultiStreamReadMessage(StreamIdPair[] streamIdPairs, int? countPerStream, CommandFlags flags) { // Example: XREAD COUNT 2 STREAMS mystream writers 0-0 0-0 - if (streamAndIdPairs == null) throw new ArgumentNullException(nameof(streamAndIdPairs)); - if (streamAndIdPairs.Count == 0) throw new ArgumentOutOfRangeException(nameof(streamAndIdPairs), "streamAndIdPairs must contain at least one item."); + if (streamIdPairs == null) throw new ArgumentNullException(nameof(streamIdPairs)); + if (streamIdPairs.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamIdPairs), "streamAndIdPairs must contain at least one item."); if (countPerStream.HasValue && countPerStream <= 0) { @@ -2322,7 +2274,7 @@ private Message GetMultiStreamReadMessage(IList + /// Describes a pair consisting of the Stream Key and the ID from which to read. + /// + /// + public struct StreamIdPair + { + /// + /// Initializes a value. + /// + /// The key for the stream. + /// The ID from which to begin reading the stream. + public StreamIdPair(RedisKey key, RedisValue id) + { + Key = key; + Id = id; + } + + /// + /// The key for the stream. + /// + public RedisKey Key { get; } + + /// + /// The ID from which to begin reading the stream. + /// + public RedisValue Id { get; } + + /// + /// See Object.ToString() + /// + public override string ToString() => $"{Key}: {Id}"; + } +} From 42ce25594332b55727652673ac5eb30913040745 Mon Sep 17 00:00:00 2001 From: ttingen Date: Sun, 1 Jul 2018 20:09:12 -0400 Subject: [PATCH 13/15] Method renames and cleanup. - Renamed the following API methods (includes the async equivalents): StreamClaimMessages => StreamClaim StreamClaimMessagesReturningIds => StreamClaimIdsOnly StreamMessagesDelete => StreamDelete - Fixed the Message.Create method call of the StreamCreateConsumerGroupAsync method. - Miscellaneous cleanup. --- .../DatabaseWrapperTests.cs | 12 +++--- StackExchange.Redis.Tests/Streams.cs | 38 +++++++++---------- StackExchange.Redis.Tests/WrapperBaseTests.cs | 12 +++--- .../Redis/Interfaces/IDatabase.cs | 38 +++++++++---------- .../Redis/Interfaces/IDatabaseAsync.cs | 38 +++++++++---------- .../KeyspaceIsolation/DatabaseWrapper.cs | 12 +++--- .../Redis/KeyspaceIsolation/WrapperBase.cs | 12 +++--- .../StackExchange/Redis/RedisDatabase.cs | 34 +++++++++-------- .../StackExchange/Redis/StreamIdPair.cs | 3 +- 9 files changed, 100 insertions(+), 99 deletions(-) diff --git a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs index add2b6533..255d1c263 100644 --- a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs +++ b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs @@ -816,16 +816,16 @@ public void StreamAdd_2() public void StreamClaimMessages() { var messageIds = new RedisValue[0]; - wrapper.StreamClaimMessages("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamClaimMessages("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); + wrapper.StreamClaim("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamClaim("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); } [Fact] public void StreamClaimMessagesReturningIds() { var messageIds = new RedisValue[0]; - wrapper.StreamClaimMessagesReturningIds("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamClaimMessagesReturningIds("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); + wrapper.StreamClaimIdsOnly("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamClaimIdsOnly("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); } [Fact] @@ -867,8 +867,8 @@ public void StreamLength() public void StreamMessagesDelete() { var messageIds = new RedisValue[0] { }; - wrapper.StreamMessagesDelete("key", messageIds, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamMessagesDelete("prefix:key", messageIds, CommandFlags.HighPriority)); + wrapper.StreamDelete("key", messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamDelete("prefix:key", messageIds, CommandFlags.HighPriority)); } [Fact] diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index f72e7ba02..342ad77b9 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using Xunit; using Xunit.Abstractions; @@ -51,7 +50,7 @@ public void StreamAddMultipleValuePairsWithAutoId() var key = GetUniqueKey("multiple_value_pairs"); - var fields = new NameValueEntry[2] + var fields = new NameValueEntry[] { new NameValueEntry("field1", "value1"), new NameValueEntry("field2", "value2") @@ -101,7 +100,7 @@ public void StreamAddMultipleValuePairsWithManualId() var db = conn.GetDatabase(); - var fields = new NameValueEntry[2] + var fields = new NameValueEntry[] { new NameValueEntry("field1", "value1"), new NameValueEntry("field2", "value2") @@ -133,7 +132,7 @@ public void StreamConsumerGroupWithNoConsumers() db.StreamAdd(key, "field1", "value1"); // Create a group - var result = db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Query redis for the group consumers, expect an empty list in response. var consumers = db.StreamConsumerInfo(key, groupName); @@ -308,10 +307,10 @@ public void StreamConsumerGroupClaimMessages() db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Read a single message into the first consumer. - var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.ReadMinValue, 1); + db.StreamReadGroup(key, groupName, consumer1, StreamConstants.ReadMinValue, 1); // Read the remaining messages into the second consumer. - var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); // Claim the 3 messages consumed by consumer2 for consumer1. @@ -323,7 +322,7 @@ public void StreamConsumerGroupClaimMessages() consumer2); // Claim the messages for consumer1. - var messages = db.StreamClaimMessages(key, + var messages = db.StreamClaim(key, groupName, consumer1, 0, // Min message idle time @@ -335,6 +334,7 @@ public void StreamConsumerGroupClaimMessages() Assert.NotNull(pendingSummary.Consumers); Assert.True(pendingSummary.Consumers.Length == 1); Assert.Equal(4, pendingSummary.Consumers[0].PendingMessageCount); + Assert.True(pendingMessages.Length == messages.Length); } } @@ -375,7 +375,7 @@ public void StreamConsumerGroupClaimMessagesReturningIds() consumer2); // Claim the messages for consumer1. - var messageIds = db.StreamClaimMessagesReturningIds(key, + var messageIds = db.StreamClaimIdsOnly(key, groupName, consumer1, 0, // Min message idle time @@ -581,7 +581,7 @@ public void StreamDeleteMessage() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - var deletedCount = db.StreamMessagesDelete(key, new RedisValue[1] { id3 }, CommandFlags.None); + var deletedCount = db.StreamDelete(key, new RedisValue[] { id3 }); var messages = db.StreamRange(key, "-", "+"); Assert.Equal(1, deletedCount); @@ -605,7 +605,7 @@ public void StreamDeleteMessages() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - var deletedCount = db.StreamMessagesDelete(key, new RedisValue[2] { id2, id3 }, CommandFlags.None); + var deletedCount = db.StreamDelete(key, new RedisValue[] { id2, id3 }, CommandFlags.None); var messages = db.StreamRange(key, "-", "+"); Assert.Equal(2, deletedCount); @@ -733,7 +733,7 @@ public void StreamInfoGetWithEmptyStream() // and last-entry messages should be null. var id = db.StreamAdd(key, "field1", "value1"); - db.StreamMessagesDelete(key, new RedisValue[1] { id }); + db.StreamDelete(key, new RedisValue[] { id }); Assert.Equal(0, db.StreamLength(key)); @@ -755,7 +755,7 @@ public void StreamNoConsumerGroups() var db = conn.GetDatabase(); - var id = db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field1", "value1"); var groups = db.StreamGroupInfo(key); @@ -777,7 +777,7 @@ public void StreamPendingNoMessagesOrConsumers() var db = conn.GetDatabase(); var id = db.StreamAdd(key, "field1", "value1"); - db.StreamMessagesDelete(key, new RedisValue[1] { id }); + db.StreamDelete(key, new RedisValue[] { id }); db.StreamCreateConsumerGroup(key, groupName, "0-0"); @@ -831,7 +831,7 @@ public void StreamReadEmptyStream() var id1 = db.StreamAdd(key, "field1", "value1"); // Delete the key to empty the stream. - db.StreamMessagesDelete(key, new RedisValue[1] { id1 }); + db.StreamDelete(key, new RedisValue[] { id1 }); var len = db.StreamLength(key); // Read the entire stream from the beginning. @@ -859,8 +859,8 @@ public void StreamReadEmptyStreams() var id2 = db.StreamAdd(key2, "field2", "value2"); // Delete the key to empty the stream. - db.StreamMessagesDelete(key1, new RedisValue[1] { id1 }); - db.StreamMessagesDelete(key2, new RedisValue[1] { id2 }); + db.StreamDelete(key1, new RedisValue[] { id1 }); + db.StreamDelete(key2, new RedisValue[] { id2 }); var len1 = db.StreamLength(key1); var len2 = db.StreamLength(key2); @@ -880,8 +880,6 @@ public void StreamReadEmptyStreams() [Fact] public void StreamReadExpectedExceptionInvalidCountMultipleStream() { - var key = GetUniqueKey("read_exception_invalid_count_multiple"); - using (var conn = Create()) { Skip.IfMissingFeature(conn, nameof(RedisFeatures.Streams), r => r.Streams); @@ -1069,7 +1067,7 @@ public void StreamReadMultipleStreamsWithEmptyResponse() var id3 = db.StreamAdd(key2, "field3", "value3"); var id4 = db.StreamAdd(key2, "field4", "value4"); - var streamList = new StreamIdPair[2] + var streamList = new StreamIdPair[] { // Read past the end of both streams. new StreamIdPair(key1, id2), @@ -1141,7 +1139,7 @@ public void StreamReadRangeOfEmptyStream() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var deleted = db.StreamMessagesDelete(key, new RedisValue[] { id1, id2 }); + var deleted = db.StreamDelete(key, new RedisValue[] { id1, id2 }); var entries = db.StreamRange(key, "-", "+"); diff --git a/StackExchange.Redis.Tests/WrapperBaseTests.cs b/StackExchange.Redis.Tests/WrapperBaseTests.cs index c1e873f18..280754431 100644 --- a/StackExchange.Redis.Tests/WrapperBaseTests.cs +++ b/StackExchange.Redis.Tests/WrapperBaseTests.cs @@ -774,16 +774,16 @@ public void StreamAddAsync_2() public void StreamClaimMessagesAsync() { var messageIds = new RedisValue[0]; - wrapper.StreamClaimMessagesAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamClaimMessagesAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); + wrapper.StreamClaimAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamClaimAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); } [Fact] public void StreamClaimMessagesReturningIdsAsync() { var messageIds = new RedisValue[0]; - wrapper.StreamClaimMessagesReturningIdsAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamClaimMessagesReturningIdsAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); + wrapper.StreamClaimIdsOnlyAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamClaimIdsOnlyAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.HighPriority)); } [Fact] @@ -825,8 +825,8 @@ public void StreamLengthAsync() public void StreamMessagesDeleteAsync() { var messageIds = new RedisValue[0] { }; - wrapper.StreamMessagesDeleteAsync("key", messageIds, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamMessagesDeleteAsync("prefix:key", messageIds, CommandFlags.HighPriority)); + wrapper.StreamDeleteAsync("key", messageIds, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamDeleteAsync("prefix:key", messageIds, CommandFlags.HighPriority)); } [Fact] diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs index 2d261322c..20c81c26f 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs @@ -1449,7 +1449,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro - RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + RedisStreamEntry[] StreamClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the IDs for the claimed message(s). @@ -1462,7 +1462,17 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The message IDs for the messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro - RedisValue[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + RedisValue[] StreamClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the consumers for the given consumer group. This is the equivalent of calling "XINFO GROUPS key group". + /// + /// The key of the stream. + /// The consumer group name. + /// The flags to use for this operation. + /// An instance of for each of the consumer group's consumers. + /// https://redis.io/topics/streams-intro + StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); /// /// Create a consumer group for the given stream. @@ -1476,13 +1486,14 @@ IEnumerable SortedSetScan(RedisKey key, bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None); /// - /// Retrieve information about the given stream. This is the equivalent of calling "XINFO STREAM key". + /// Delete messages in the stream. This method does not delete the stream. /// /// The key of the stream. + /// The IDs of the messages to delete. /// The flags to use for this operation. - /// A instance with information about the stream. + /// Returns the number of messages successfully deleted from the stream. /// https://redis.io/topics/streams-intro - StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None); + long StreamDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Retrieve information about the groups created for the given stream. This is the equivalent of calling "XINFO GROUPS key". @@ -1494,14 +1505,13 @@ IEnumerable SortedSetScan(RedisKey key, StreamGroupInfo[] StreamGroupInfo(RedisKey key, CommandFlags flags = CommandFlags.None); /// - /// Retrieve information about the consumers for the given consumer group. This is the equivalent of calling "XINFO GROUPS key group". + /// Retrieve information about the given stream. This is the equivalent of calling "XINFO STREAM key". /// /// The key of the stream. - /// The consumer group name. /// The flags to use for this operation. - /// An instance of for each of the consumer group's consumers. + /// A instance with information about the stream. /// https://redis.io/topics/streams-intro - StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None); /// /// Return the number of entries in a stream. @@ -1512,16 +1522,6 @@ IEnumerable SortedSetScan(RedisKey key, /// https://redis.io/commands/xlen long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None); - /// - /// Delete messages in the stream. - /// - /// The key of the stream. - /// The IDs of the messages to delete. - /// The flags to use for this operation. - /// Returns the number of messages successfully deleted from the stream. - /// https://redis.io/topics/streams-intro - long StreamMessagesDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); - /// /// View information about pending messages for a stream. A pending message is a message read using StreamReadGroup (XREADGROUP) but not yet acknowledged. /// diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs index cc8357460..335ac3a3a 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs @@ -1359,7 +1359,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro - Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + Task StreamClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the IDs for the claimed message(s). @@ -1372,7 +1372,17 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The message IDs for the messages successfully claimed by the given consumer. /// https://redis.io/topics/streams-intro - Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + Task StreamClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the consumers for the given consumer group. This is the equivalent of calling "XINFO GROUPS key group". + /// + /// The key of the stream. + /// The consumer group name. + /// The flags to use for this operation. + /// An instance of for each of the consumer group's consumers. + /// https://redis.io/topics/streams-intro + Task StreamConsumerInfoAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); /// /// Create a consumer group for the given stream. @@ -1386,13 +1396,14 @@ Task SortedSetRangeByValueAsync(RedisKey key, Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None); /// - /// Retrieve information about the given stream. This is the equivalent of calling "XINFO STREAM key". + /// Delete messages in the stream. This method does not delete the stream. /// /// The key of the stream. + /// The IDs of the messages to delete. /// The flags to use for this operation. - /// A instance with information about the stream. + /// Returns the number of messages successfully deleted from the stream. /// https://redis.io/topics/streams-intro - Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); /// /// Retrieve information about the groups created for the given stream. This is the equivalent of calling "XINFO GROUPS key". @@ -1404,14 +1415,13 @@ Task SortedSetRangeByValueAsync(RedisKey key, Task StreamGroupInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None); /// - /// Retrieve information about the consumers for the given consumer group. This is the equivalent of calling "XINFO GROUPS key group". + /// Retrieve information about the given stream. This is the equivalent of calling "XINFO STREAM key". /// /// The key of the stream. - /// The consumer group name. /// The flags to use for this operation. - /// An instance of for each of the consumer group's consumers. + /// A instance with information about the stream. /// https://redis.io/topics/streams-intro - Task StreamConsumerInfoAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None); /// /// Return the number of entries in a stream. @@ -1422,16 +1432,6 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// https://redis.io/commands/xlen Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - /// - /// Delete messages in the stream. - /// - /// The key of the stream. - /// The IDs of the messages to delete. - /// The flags to use for this operation. - /// Returns the number of messages successfully deleted from the stream. - /// https://redis.io/topics/streams-intro - Task StreamMessagesDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); - /// /// View information about pending messages for a stream. A pending message is a message read using StreamReadGroup (XREADGROUP) but not yet acknowledged. /// diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs index 506bbb91a..e2ea6d9b2 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -607,14 +607,14 @@ public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, string m return Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); } - public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - return Inner.StreamClaimMessages(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + return Inner.StreamClaim(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } - public RedisValue[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public RedisValue[] StreamClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - return Inner.StreamClaimMessagesReturningIds(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + return Inner.StreamClaimIdsOnly(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) @@ -642,9 +642,9 @@ public long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None) return Inner.StreamLength(ToInner(key), flags); } - public long StreamMessagesDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public long StreamDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - return Inner.StreamMessagesDelete(ToInner(key), messageIds, flags); + return Inner.StreamDelete(ToInner(key), messageIds, flags); } public StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs index 19d45cab2..e01a708a4 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs @@ -586,14 +586,14 @@ public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPair return Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); } - public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - return Inner.StreamClaimMessagesAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + return Inner.StreamClaimAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } - public Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - return Inner.StreamClaimMessagesReturningIdsAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + return Inner.StreamClaimIdsOnlyAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) @@ -621,9 +621,9 @@ public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFl return Inner.StreamLengthAsync(ToInner(key), flags); } - public Task StreamMessagesDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { - return Inner.StreamMessagesDeleteAsync(ToInner(key), messageIds, flags); + return Inner.StreamDeleteAsync(ToInner(key), messageIds, flags); } public Task StreamPendingAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index 14851384f..1d11f60bd 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -1651,7 +1651,7 @@ public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPair return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = GetStreamClaimMessage(key, consumerGroup, @@ -1664,7 +1664,7 @@ public RedisStreamEntry[] StreamClaimMessages(RedisKey key, RedisValue consumerG return ExecuteSync(msg, ResultProcessor.SingleStream); } - public Task StreamClaimMessagesAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = GetStreamClaimMessage(key, consumerGroup, @@ -1677,7 +1677,7 @@ public Task StreamClaimMessagesAsync(RedisKey key, RedisValu return ExecuteAsync(msg, ResultProcessor.SingleStream); } - public RedisValue[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public RedisValue[] StreamClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = GetStreamClaimMessage(key, consumerGroup, @@ -1690,7 +1690,7 @@ public RedisValue[] StreamClaimMessagesReturningIds(RedisKey key, RedisValue con return ExecuteSync(msg, ResultProcessor.RedisValueArray); } - public Task StreamClaimMessagesReturningIdsAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = GetStreamClaimMessage(key, consumerGroup, @@ -1708,7 +1708,7 @@ public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisV var msg = Message.Create(Database, flags, RedisCommand.XGROUP, - new RedisValue[4] + new RedisValue[] { StreamConstants.Create, key.AsRedisValue(), @@ -1724,9 +1724,13 @@ public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupN var msg = Message.Create(Database, flags, RedisCommand.XGROUP, - key, - groupName, - readFrom); + new RedisValue[] + { + StreamConstants.Create, + key.AsRedisValue(), + groupName, + readFrom + }); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -1736,7 +1740,7 @@ public StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupNam var msg = Message.Create(Database, flags, RedisCommand.XINFO, - new RedisValue[3] + new RedisValue[] { StreamConstants.Consumers, key.AsRedisValue(), @@ -1751,7 +1755,7 @@ public Task StreamConsumerInfoAsync(RedisKey key, RedisVal var msg = Message.Create(Database, flags, RedisCommand.XINFO, - new RedisValue[3] + new RedisValue[] { StreamConstants.Consumers, key.AsRedisValue(), @@ -1797,7 +1801,7 @@ public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFl return ExecuteAsync(msg, ResultProcessor.Int64); } - public long StreamMessagesDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public long StreamDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, @@ -1808,7 +1812,7 @@ public long StreamMessagesDelete(RedisKey key, RedisValue[] messageIds, CommandF return ExecuteSync(msg, ResultProcessor.Int64); } - public Task StreamMessagesDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, @@ -2546,7 +2550,7 @@ private Message GetSortedSetRemoveRangeByScoreMessage(RedisKey key, double start private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags) { - var values = new RedisValue[3] + var values = new RedisValue[] { key.AsRedisValue(), groupName, @@ -2606,7 +2610,7 @@ private Message GetStreamAddMessage(RedisKey key, RedisValue messageId, int? max } values[offset++] = streamPair.Name; - values[offset++] = streamPair.Value; + values[offset] = streamPair.Value; return Message.Create(Database, flags, RedisCommand.XADD, key, values); } @@ -2680,7 +2684,7 @@ private Message GetStreamClaimMessage(RedisKey key, RedisValue consumerGroup, Re if (returnJustIds) { - values[offset++] = StreamConstants.JustId; + values[offset] = StreamConstants.JustId; } return Message.Create(Database, flags, RedisCommand.XCLAIM, values); diff --git a/StackExchange.Redis/StackExchange/Redis/StreamIdPair.cs b/StackExchange.Redis/StackExchange/Redis/StreamIdPair.cs index 2b1179047..198396946 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamIdPair.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamIdPair.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; - + namespace StackExchange.Redis { /// From 5639f3b399153bd00df68aa2a651608c577a9bbb Mon Sep 17 00:00:00 2001 From: ttingen Date: Mon, 2 Jul 2018 22:13:58 -0400 Subject: [PATCH 14/15] Parameter type updates and StreamConstants changed to be internal. - messageId on StreamAdd methods was changed from string to RedisValue? and is defaulted to null. When null, the API will send the auto-generated ID option ("*"). - StreamConstants accessibility was updated from public to internal. - minId & maxId input parameters were updated to be of type RedisValue?, sends StreamConstants.ReadMinValue & StreamConstants.ReadMaxValue when null. - readFromId on StreamCreateConsumerGroup updated to type RedisValue?, sends StreamConstants.NewMessages when null. - readFromId on StreamReadGroup updated to type RedisValue?, sends StreamConstants.UndeliveredMessages when null. --- .../DatabaseWrapperTests.cs | 4 +- StackExchange.Redis.Tests/Streams.cs | 22 +++----- StackExchange.Redis.Tests/WrapperBaseTests.cs | 4 +- .../Redis/Interfaces/IDatabase.cs | 24 ++++---- .../Redis/Interfaces/IDatabaseAsync.cs | 26 ++++----- .../KeyspaceIsolation/DatabaseWrapper.cs | 14 ++--- .../Redis/KeyspaceIsolation/WrapperBase.cs | 14 ++--- .../StackExchange/Redis/RedisDatabase.cs | 55 ++++++++++--------- .../StackExchange/Redis/StreamConstants.cs | 14 ++--- 9 files changed, 86 insertions(+), 91 deletions(-) diff --git a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs index 255d1c263..a388821d7 100644 --- a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs +++ b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs @@ -881,8 +881,8 @@ public void StreamPendingInfoGet() [Fact] public void StreamPendingMessageInfoGet() { - wrapper.StreamPendingMessages("key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingMessages("prefix:key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority)); + wrapper.StreamPendingMessages("key", "group", 10, RedisValue.Null, null, null, CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessages("prefix:key", "group", 10, RedisValue.Null, null, null, CommandFlags.HighPriority)); } [Fact] diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index 342ad77b9..744bfdfdb 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -304,20 +304,18 @@ public void StreamConsumerGroupClaimMessages() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "0-0"); // Read a single message into the first consumer. - db.StreamReadGroup(key, groupName, consumer1, StreamConstants.ReadMinValue, 1); + db.StreamReadGroup(key, groupName, consumer1, count: 1); // Read the remaining messages into the second consumer. - db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + db.StreamReadGroup(key, groupName, consumer2); // Claim the 3 messages consumed by consumer2 for consumer1. // Get the pending messages for consumer2. var pendingMessages = db.StreamPendingMessages(key, groupName, - StreamConstants.ReadMinValue, - StreamConstants.ReadMaxValue, 10, consumer2); @@ -369,8 +367,6 @@ public void StreamConsumerGroupClaimMessagesReturningIds() // Get the pending messages for consumer2. var pendingMessages = db.StreamPendingMessages(key, groupName, - StreamConstants.ReadMinValue, - StreamConstants.ReadMaxValue, 10, consumer2); @@ -430,12 +426,10 @@ public void StreamConsumerGroupViewPendingInfoWhenNothingPending() var id1 = db.StreamAdd(key, "field1", "value1"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "0-0"); var pendingMessages = db.StreamPendingMessages(key, groupName, - StreamConstants.ReadMinValue, - StreamConstants.ReadMaxValue, 10, consumerName: RedisValue.Null); @@ -508,13 +502,13 @@ public void StreamConsumerGroupViewPendingMessageInfo() db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); // Read a single message into the first consumer. - var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.UndeliveredMessages, 1); + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, count: 1); // Read the remaining messages into the second consumer. - var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2); // Get the pending info about the messages themselves. - var pendingMessageInfoList = db.StreamPendingMessages(key, groupName, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 10, RedisValue.Null); + var pendingMessageInfoList = db.StreamPendingMessages(key, groupName, 10, RedisValue.Null); Assert.NotNull(pendingMessageInfoList); Assert.Equal(4, pendingMessageInfoList.Length); @@ -555,8 +549,6 @@ public void StreamConsumerGroupViewPendingMessageInfoForConsumer() // Get the pending info about the messages themselves. var pendingMessageInfoList = db.StreamPendingMessages(key, groupName, - StreamConstants.ReadMinValue, - StreamConstants.ReadMaxValue, 10, consumer2); diff --git a/StackExchange.Redis.Tests/WrapperBaseTests.cs b/StackExchange.Redis.Tests/WrapperBaseTests.cs index 280754431..0bf14087d 100644 --- a/StackExchange.Redis.Tests/WrapperBaseTests.cs +++ b/StackExchange.Redis.Tests/WrapperBaseTests.cs @@ -839,8 +839,8 @@ public void StreamPendingInfoGetAsync() [Fact] public void StreamPendingMessageInfoGetAsync() { - wrapper.StreamPendingMessagesAsync("key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority); - mock.Verify(_ => _.StreamPendingMessagesAsync("prefix:key", "group", "-", "+", 10, RedisValue.Null, CommandFlags.HighPriority)); + wrapper.StreamPendingMessagesAsync("key", "group", 10, RedisValue.Null, "-", "+", CommandFlags.HighPriority); + mock.Verify(_ => _.StreamPendingMessagesAsync("prefix:key", "group", 10, RedisValue.Null, "-", "+", CommandFlags.HighPriority)); } [Fact] diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs index 20c81c26f..8511570f7 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabase.cs @@ -1423,7 +1423,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The ID of the newly created message. /// https://redis.io/commands/xadd - RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1436,7 +1436,7 @@ IEnumerable SortedSetScan(RedisKey key, /// The flags to use for this operation. /// The ID of the newly created message. /// https://redis.io/commands/xadd - RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). @@ -1479,11 +1479,11 @@ IEnumerable SortedSetScan(RedisKey key, /// /// The key of the stream. /// The name of the group to create. - /// The beginning position in the stream from which to read. Pass "$" or to read only new messages. + /// The beginning position in the stream from which to read. If null, the method will send the option ("$") to only read new messages. /// The flags to use for this operation. /// True if the group was created. /// https://redis.io/topics/streams-intro - bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None); + bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? readFrom = null, CommandFlags flags = CommandFlags.None); /// /// Delete messages in the stream. This method does not delete the stream. @@ -1538,28 +1538,28 @@ IEnumerable SortedSetScan(RedisKey key, /// /// The key of the stream. /// The name of the consumer group. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of pending messages to return. /// The consumer name for the pending messages. Pass RedisValue.Null to include pending messages for all consumers. + /// The minimum ID from which to read the stream of pending messages. The method will default to reading from the beginning of the stream. + /// The maximum ID to read to within the stream of pending messages. The method will default to reading to the end of the stream. /// The flags to use for this operation. /// An instance of for each pending message. /// Equivalent of calling XPENDING key group start-id end-id count consumer-name. /// https://redis.io/commands/xpending - StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); + StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None); /// /// Read a stream using the given range of IDs. /// /// The key of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream. The method will default to reading from the beginning of the stream. + /// The maximum ID to read to within the stream. The method will default to reading to the end of the stream. /// The maximum number of messages to return. /// The order of the messages. will execute XRANGE and wil execute XREVRANGE. /// The flags to use for this operation. /// Returns an instance of for each message returned. /// https://redis.io/commands/xrange - RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None); + RedisStreamEntry[] StreamRange(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None); /// /// Read from a single stream. @@ -1590,12 +1590,12 @@ IEnumerable SortedSetScan(RedisKey key, /// The key of the stream. /// The name of the consumer group. /// The consumer name. - /// The ID from within the stream to begin reading. + /// The ID from within the stream to begin reading. If null, the method will send the option (">") to only read new, previously undelivered messages. /// The maximum number of messages to return. /// The flags to use for this operation. /// Returns an instance of for each message returned. /// https://redis.io/commands/xreadgroup - RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None); + RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? readFromId = null, int? count = null, CommandFlags flags = CommandFlags.None); /// /// Trim the stream to a specified maximum length. diff --git a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs index 335ac3a3a..438079abe 100644 --- a/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs +++ b/StackExchange.Redis/StackExchange/Redis/Interfaces/IDatabaseAsync.cs @@ -1333,7 +1333,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. If key does not exist, a new key holding a stream is created. The command returns the ID of the newly created stream entry. @@ -1346,7 +1346,7 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The flags to use for this operation. /// The ID of the newly created message. /// https://redis.io/commands/xadd - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None); /// /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. This method returns the complete message for the claimed message(s). @@ -1389,11 +1389,11 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// /// The key of the stream. /// The name of the group to create. - /// The beginning position in the stream from which to read. Pass "$" or to read only new messages. + /// The beginning position in the stream from which to read. If null, the method will send the option ("$") to only read new messages. /// The flags to use for this operation. /// True if the group was created. /// https://redis.io/topics/streams-intro - Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None); + Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? readFrom = null, CommandFlags flags = CommandFlags.None); /// /// Delete messages in the stream. This method does not delete the stream. @@ -1448,34 +1448,34 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// /// The key of the stream. /// The name of the consumer group. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. /// The maximum number of pending messages to return. /// The consumer name for the pending messages. Pass RedisValue.Null to include pending messages for all consumers. + /// The minimum ID from which to read the stream of pending messages. The method will default to reading from the beginning of the stream. + /// The maximum ID to read to within the stream of pending messages. The method will default to reading to the end of the stream. /// The flags to use for this operation. /// An instance of for each pending message. /// Equivalent of calling XPENDING key group start-id end-id count consumer-name. /// https://redis.io/commands/xpending - Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None); + Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None); /// /// Read a stream using the given range of IDs. /// /// The key of the stream. - /// The minimum ID from which to read the stream of pending messages. Pass "-" or to read from the beginning of the stream" /> - /// The maximum ID to read to within the stream of pending messages. Pass "+" or to read to the end of the stream. + /// The minimum ID from which to read the stream. The method will default to reading from the beginning of the stream. + /// The maximum ID to read to within the stream. The method will default to reading to the end of the stream. /// The maximum number of messages to return. /// The order of the messages. will execute XRANGE and wil execute XREVRANGE. /// The flags to use for this operation. /// Returns an instance of for each message returned. /// https://redis.io/commands/xrange - Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None); + Task StreamRangeAsync(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None); /// /// Read from a single stream. /// /// The key of the stream. - /// The position from within the stream to begin reading. + /// The message ID from within the stream to begin reading. /// The maximum number of messages to return. /// The flags to use for this operation. /// Returns an instance of for each message returned. @@ -1500,12 +1500,12 @@ Task SortedSetRangeByValueAsync(RedisKey key, /// The key of the stream. /// The name of the consumer group. /// The consumer name. - /// The ID from within the stream to begin reading. + /// The ID from within the stream to begin reading. If null, the method will send the option (">") to only read new, previously undelivered messages. /// The maximum number of messages to return. /// The flags to use for this operation. /// Returns an instance of for each message returned. /// https://redis.io/commands/xreadgroup - Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None); + Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? readFromId = null, int? count = null, CommandFlags flags = CommandFlags.None); /// /// Trim the stream to a specified maximum length. diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs index e2ea6d9b2..34a96c2b5 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -597,12 +597,12 @@ public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] m return Inner.StreamAcknowledge(ToInner(key), groupName, messageIds, flags); } - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAdd(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, flags); } - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); } @@ -617,7 +617,7 @@ public RedisValue[] StreamClaimIdsOnly(RedisKey key, RedisValue consumerGroup, R return Inner.StreamClaimIdsOnly(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } - public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? readFrom = null, CommandFlags flags = CommandFlags.None) { return Inner.StreamCreateConsumerGroup(ToInner(key), groupName, readFrom, flags); } @@ -652,12 +652,12 @@ public StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, Comma return Inner.StreamPending(ToInner(key), groupName, flags); } - public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None) { - return Inner.StreamPendingMessages(ToInner(key), groupName, minId, maxId, count, consumerName, flags); + return Inner.StreamPendingMessages(ToInner(key), groupName, count, consumerName, minId, maxId, flags); } - public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { return Inner.StreamRange(ToInner(key), minId, maxId, count, order, flags); } @@ -672,7 +672,7 @@ public RedisStream[] StreamRead(StreamIdPair[] streamIdPairs, int? countPerStrea return Inner.StreamRead(streamIdPairs, countPerStream, flags); } - public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? readFromId = null, int? count = null, CommandFlags flags = CommandFlags.None) { return Inner.StreamReadGroup(ToInner(key), groupName, consumerName, readFromId, count, flags); } diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs index e01a708a4..3a5b1e58a 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs +++ b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs @@ -576,12 +576,12 @@ public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, Red return Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageIds, flags); } - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAddAsync(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, flags); } - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { return Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); } @@ -596,7 +596,7 @@ public Task StreamClaimIdsOnlyAsync(RedisKey key, RedisValue consu return Inner.StreamClaimIdsOnlyAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); } - public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? readFrom = null, CommandFlags flags = CommandFlags.None) { return Inner.StreamCreateConsumerGroupAsync(ToInner(key), groupName, readFrom, flags); } @@ -631,12 +631,12 @@ public Task StreamPendingAsync(RedisKey key, RedisValue group return Inner.StreamPendingAsync(ToInner(key), groupName, flags); } - public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None) { - return Inner.StreamPendingMessagesAsync(ToInner(key), groupName, minId, maxId, count, consumerName, flags); + return Inner.StreamPendingMessagesAsync(ToInner(key), groupName, count, consumerName, minId, maxId, flags); } - public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + public Task StreamRangeAsync(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { return Inner.StreamRangeAsync(ToInner(key), minId, maxId, count, order, flags); } @@ -651,7 +651,7 @@ public Task StreamReadAsync(StreamIdPair[] streamIdPairs, int? co return Inner.StreamReadAsync(streamIdPairs, countPerStream, flags); } - public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? readFromId = null, int? count = null, CommandFlags flags = CommandFlags.None) { return Inner.StreamReadGroupAsync(ToInner(key), groupName, consumerName, readFromId, count, flags); } diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs index 1d11f60bd..7d17fb0f6 100644 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs @@ -1603,10 +1603,10 @@ public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, Red return ExecuteAsync(msg, ResultProcessor.Int64); } - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - messageId, + messageId ?? StreamConstants.AutoGeneratedId, maxLength, useApproximateMaxLength, new NameValueEntry(streamField, streamValue), @@ -1615,10 +1615,10 @@ public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue str return ExecuteSync(msg, ResultProcessor.RedisValue); } - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - messageId, + messageId ?? StreamConstants.AutoGeneratedId, maxLength, useApproximateMaxLength, new NameValueEntry(streamField, streamValue), @@ -1627,10 +1627,10 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamField, Red return ExecuteAsync(msg, ResultProcessor.RedisValue); } - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - messageId, + messageId ?? StreamConstants.AutoGeneratedId, maxLength, useApproximateMaxLength, streamPairs, @@ -1639,10 +1639,10 @@ public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, string m return ExecuteSync(msg, ResultProcessor.RedisValue); } - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, string messageId = "*", int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage(key, - messageId, + messageId ?? StreamConstants.AutoGeneratedId, maxLength, useApproximateMaxLength, streamPairs, @@ -1703,7 +1703,7 @@ public Task StreamClaimIdsOnlyAsync(RedisKey key, RedisValue consu return ExecuteAsync(msg, ResultProcessor.RedisValueArray); } - public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? readFrom = null, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, @@ -1713,13 +1713,13 @@ public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisV StreamConstants.Create, key.AsRedisValue(), groupName, - readFrom + readFrom ?? StreamConstants.NewMessages }); return ExecuteSync(msg, ResultProcessor.Boolean); } - public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue readFrom, CommandFlags flags = CommandFlags.None) + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? readFrom = null, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, @@ -1729,7 +1729,7 @@ public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupN StreamConstants.Create, key.AsRedisValue(), groupName, - readFrom + readFrom ?? StreamConstants.NewMessages }); return ExecuteAsync(msg, ResultProcessor.Boolean); @@ -1835,25 +1835,25 @@ public Task StreamPendingAsync(RedisKey key, RedisValue group return ExecuteAsync(msg, ResultProcessor.StreamPendingInfo); } - public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None) { var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, consumerName, flags); return ExecuteSync(msg, ResultProcessor.StreamPendingMessages); } - public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None) { var msg = GetStreamPendingMessagesMessage(key, groupName, minId, maxId, count, consumerName, flags); return ExecuteAsync(msg, ResultProcessor.StreamPendingMessages); } - public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamRange(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) { var msg = GetStreamRangeMessage(key, minId, maxId, count, messageOrder, flags); return ExecuteSync(msg, ResultProcessor.SingleStream); } - public Task StreamRangeAsync(RedisKey key, RedisValue minId, RedisValue maxId, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) + public Task StreamRangeAsync(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) { var msg = GetStreamRangeMessage(key, minId, maxId, count, messageOrder, flags); return ExecuteAsync(msg, ResultProcessor.SingleStream); @@ -1883,13 +1883,13 @@ public Task StreamReadAsync(StreamIdPair[] streamIdPairs, int? co return ExecuteAsync(msg, ResultProcessor.MultiStream); } - public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) + public RedisStreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? readFromId = null, int? count = null, CommandFlags flags = CommandFlags.None) { var msg = GetStreamReadGroupMessage(key, groupName, consumerName, readFromId, count, flags); return ExecuteSync(msg, ResultProcessor.SingleStreamWithNameSkip); } - public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count = null, CommandFlags flags = CommandFlags.None) + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? readFromId = null, int? count = null, CommandFlags flags = CommandFlags.None) { var msg = GetStreamReadGroupMessage(key, groupName, consumerName, readFromId, count, flags); return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip); @@ -2690,7 +2690,7 @@ private Message GetStreamClaimMessage(RedisKey key, RedisValue consumerGroup, Re return Message.Create(Database, flags, RedisCommand.XCLAIM, values); } - private Message GetStreamPendingMessagesMessage(RedisKey key, RedisValue groupName, RedisValue minId, RedisValue maxId, int count, RedisValue consumerName, CommandFlags flags) + private Message GetStreamPendingMessagesMessage(RedisKey key, RedisValue groupName, RedisValue? minId, RedisValue? maxId, int count, RedisValue consumerName, CommandFlags flags) { // > XPENDING mystream mygroup - + 10 [consumer name] // 1) 1) 1526569498055 - 0 @@ -2713,8 +2713,8 @@ private Message GetStreamPendingMessagesMessage(RedisKey key, RedisValue groupNa values[0] = key.AsRedisValue(); values[1] = groupName; - values[2] = minId; - values[3] = maxId; + values[2] = minId ?? StreamConstants.ReadMinValue; + values[3] = maxId ?? StreamConstants.ReadMaxValue; values[4] = count; if (consumerName != RedisValue.Null) @@ -2728,17 +2728,20 @@ private Message GetStreamPendingMessagesMessage(RedisKey key, RedisValue groupNa values); } - private Message GetStreamRangeMessage(RedisKey key, RedisValue minId, RedisValue maxId, int? count, Order messageOrder, CommandFlags flags) + private Message GetStreamRangeMessage(RedisKey key, RedisValue? minId, RedisValue? maxId, int? count, Order messageOrder, CommandFlags flags) { if (count.HasValue && count <= 0) { throw new ArgumentOutOfRangeException(nameof(count), "count must be greater than 0."); } + var actualMin = minId ?? StreamConstants.ReadMinValue; + var actualMax = maxId ?? StreamConstants.ReadMaxValue; + var values = new RedisValue[2 + (count.HasValue ? 2 : 0)]; - values[0] = (messageOrder == Order.Ascending ? minId : maxId); - values[1] = (messageOrder == Order.Ascending ? maxId : minId); + values[0] = (messageOrder == Order.Ascending ? actualMin : actualMax); + values[1] = (messageOrder == Order.Ascending ? actualMax : actualMin); if (count.HasValue) { @@ -2753,7 +2756,7 @@ private Message GetStreamRangeMessage(RedisKey key, RedisValue minId, RedisValue values); } - private Message GetStreamReadGroupMessage(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue readFromId, int? count, CommandFlags flags) + private Message GetStreamReadGroupMessage(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? readFromId, int? count, CommandFlags flags) { // Example: > XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream > if (count.HasValue && count <= 0) @@ -2778,7 +2781,7 @@ private Message GetStreamReadGroupMessage(RedisKey key, RedisValue groupName, Re values[offset++] = StreamConstants.Streams; values[offset++] = key.AsRedisValue(); - values[offset] = readFromId; + values[offset] = readFromId ?? StreamConstants.UndeliveredMessages; return Message.Create(Database, flags, diff --git a/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs b/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs index 32ab068dc..1b70ed130 100644 --- a/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs +++ b/StackExchange.Redis/StackExchange/Redis/StreamConstants.cs @@ -4,37 +4,37 @@ namespace StackExchange.Redis /// /// Constants representing values used in Redis Stream commands. /// - public static class StreamConstants + internal static class StreamConstants { /// /// The "~" value used with the MAXLEN option. /// - public static readonly RedisValue ApproximateMaxLen = "~"; + internal static readonly RedisValue ApproximateMaxLen = "~"; /// /// The "*" value used with the XADD command. /// - public static readonly RedisValue AutoGeneratedId = "*"; + internal static readonly RedisValue AutoGeneratedId = "*"; /// /// The "$" value used in the XGROUP command. Indicates reading only new messages from the stream. /// - public static readonly RedisValue NewMessages = "$"; + internal static readonly RedisValue NewMessages = "$"; /// /// The "-" value used in the XRANGE, XREAD, and XREADGROUP commands. Indicates the minimum message ID from the stream. /// - public static readonly RedisValue ReadMinValue = "-"; + internal static readonly RedisValue ReadMinValue = "-"; /// /// The "+" value used in the XRANGE, XREAD, and XREADGROUP commands. Indicates the maximum message ID from the stream. /// - public static readonly RedisValue ReadMaxValue = "+"; + internal static readonly RedisValue ReadMaxValue = "+"; /// /// The ">" value used in the XREADGROUP command. Use this to read messages that have not been delivered to a consumer group. /// - public static readonly RedisValue UndeliveredMessages = ">"; + internal static readonly RedisValue UndeliveredMessages = ">"; internal static readonly RedisValue Consumers = "CONSUMERS"; From ed728406b63dd042301333823bc9a20211233202 Mon Sep 17 00:00:00 2001 From: ttingen Date: Mon, 2 Jul 2018 22:30:12 -0400 Subject: [PATCH 15/15] Removed remaining external references to StreamConstants (now that it's internal). --- StackExchange.Redis.Tests/Streams.cs | 60 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/StackExchange.Redis.Tests/Streams.cs b/StackExchange.Redis.Tests/Streams.cs index 744bfdfdb..f9dd10b25 100644 --- a/StackExchange.Redis.Tests/Streams.cs +++ b/StackExchange.Redis.Tests/Streams.cs @@ -59,7 +59,7 @@ public void StreamAddMultipleValuePairsWithAutoId() var db = conn.GetDatabase(); var messageId = db.StreamAdd(key, fields); - var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue); + var entries = db.StreamRange(key); Assert.True(entries.Length == 1); Assert.Equal(messageId, entries[0].Id); @@ -107,7 +107,7 @@ public void StreamAddMultipleValuePairsWithManualId() }; var messageId = db.StreamAdd(key, fields, id); - var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue); + var entries = db.StreamRange(key); Assert.Equal(id, messageId); Assert.NotNull(entries); @@ -132,7 +132,7 @@ public void StreamConsumerGroupWithNoConsumers() db.StreamAdd(key, "field1", "value1"); // Create a group - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "0-0"); // Query redis for the group consumers, expect an empty list in response. var consumers = db.StreamConsumerInfo(key, groupName); @@ -157,7 +157,7 @@ public void StreamCreateConsumerGroup() db.StreamAdd(key, "field1", "value1"); // Create a group - var result = db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + var result = db.StreamCreateConsumerGroup(key, groupName, "-"); Assert.True(result); } @@ -180,7 +180,7 @@ public void StreamConsumerGroupReadOnlyNewMessagesWithEmptyResponse() db.StreamAdd(key, "field2", "value2"); // Create a group. - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.NewMessages); + db.StreamCreateConsumerGroup(key, groupName); // Read, expect no messages var entries = db.StreamReadGroup(key, groupName, "test_consumer", "0-0"); @@ -204,7 +204,7 @@ public void StreamConsumerGroupReadFromStreamBeginning() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "field2", "value2"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "-"); var entries = db.StreamReadGroup(key, groupName, "test_consumer", "0-0"); @@ -231,7 +231,7 @@ public void StreamConsumerGroupReadFromStreamBeginningWithCount() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "-"); // Start reading after id1. var entries = db.StreamReadGroup(key, groupName, "test_consumer", id1, 2); @@ -261,7 +261,7 @@ public void StreamConsumerGroupAcknowledgeMessage() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "-"); // Read all 4 messages, they will be assigned to the consumer var entries = db.StreamReadGroup(key, groupName, consumer, "0-0"); @@ -355,13 +355,13 @@ public void StreamConsumerGroupClaimMessagesReturningIds() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "-"); // Read a single message into the first consumer. - var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.ReadMinValue, 1); + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, "-", 1); // Read the remaining messages into the second consumer. - var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2); // Claim the 3 messages consumed by consumer2 for consumer1. @@ -400,7 +400,7 @@ public void StreamConsumerGroupViewPendingInfoNoConsumers() var id1 = db.StreamAdd(key, "field1", "value1"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "-"); var pendingInfo = db.StreamPending(key, groupName); @@ -457,13 +457,13 @@ public void StreamConsumerGroupViewPendingInfoSummary() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "-"); // Read a single message into the first consumer. - var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.ReadMinValue, 1); + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, "-", 1); // Read the remaining messages into the second consumer. - var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2); var pendingInfo = db.StreamPending(key, groupName); @@ -499,7 +499,7 @@ public void StreamConsumerGroupViewPendingMessageInfo() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "-"); // Read a single message into the first consumer. var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, count: 1); @@ -538,13 +538,13 @@ public void StreamConsumerGroupViewPendingMessageInfoForConsumer() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, groupName, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, groupName, "-"); // Read a single message into the first consumer. - var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, StreamConstants.UndeliveredMessages, 1); + var consumer1Messages = db.StreamReadGroup(key, groupName, consumer1, count: 1); // Read the remaining messages into the second consumer. - var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2, StreamConstants.UndeliveredMessages); + var consumer2Messages = db.StreamReadGroup(key, groupName, consumer2); // Get the pending info about the messages themselves. var pendingMessageInfoList = db.StreamPendingMessages(key, @@ -625,14 +625,14 @@ public void StreamGroupInfoGet() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, group1, StreamConstants.ReadMinValue); - db.StreamCreateConsumerGroup(key, group2, StreamConstants.ReadMinValue); + db.StreamCreateConsumerGroup(key, group1, "-"); + db.StreamCreateConsumerGroup(key, group2, "-"); // Read a single message into the first consumer. - var consumer1Messages = db.StreamReadGroup(key, group1, consumer1, StreamConstants.UndeliveredMessages, 1); + var consumer1Messages = db.StreamReadGroup(key, group1, consumer1, count: 1); // Read the remaining messages into the second consumer. - var consumer2Messages = db.StreamReadGroup(key, group2, consumer2, StreamConstants.UndeliveredMessages); + var consumer2Messages = db.StreamReadGroup(key, group2, consumer2); var groupInfoList = db.StreamGroupInfo(key); @@ -666,9 +666,9 @@ public void StreamGroupConsumerInfoGet() var id3 = db.StreamAdd(key, "field3", "value3"); var id4 = db.StreamAdd(key, "field4", "value4"); - db.StreamCreateConsumerGroup(key, group, StreamConstants.ReadMinValue); - db.StreamReadGroup(key, group, consumer1, StreamConstants.UndeliveredMessages, 1); - db.StreamReadGroup(key, group, consumer2, StreamConstants.UndeliveredMessages); + db.StreamCreateConsumerGroup(key, group, "-"); + db.StreamReadGroup(key, group, consumer1, count: 1); + db.StreamReadGroup(key, group, consumer2); var consumerInfoList = db.StreamConsumerInfo(key, group); @@ -1109,7 +1109,7 @@ public void StreamReadRange() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue); + var entries = db.StreamRange(key); Assert.Equal(2, entries.Length); Assert.Equal(id1, entries[0].Id); @@ -1155,7 +1155,7 @@ public void StreamReadRangeWithCount() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 1); + var entries = db.StreamRange(key, count: 1); Assert.True(entries.Length == 1); Assert.Equal(id1, entries[0].Id); @@ -1176,7 +1176,7 @@ public void StreamReadRangeReverse() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, messageOrder: Order.Descending); + var entries = db.StreamRange(key, messageOrder: Order.Descending); Assert.True(entries.Length == 2); Assert.Equal(id2, entries[0].Id); @@ -1198,7 +1198,7 @@ public void StreamReadRangeReverseWithCount() var id1 = db.StreamAdd(key, "field1", "value1"); var id2 = db.StreamAdd(key, "fiedl2", "value2"); - var entries = db.StreamRange(key, StreamConstants.ReadMinValue, StreamConstants.ReadMaxValue, 1, messageOrder: Order.Descending); + var entries = db.StreamRange(key, count: 1, messageOrder: Order.Descending); Assert.True(entries.Length == 1); Assert.Equal(id2, entries[0].Id);