From 9c3822c481423c5bd84b60567c49e1b22564c703 Mon Sep 17 00:00:00 2001 From: Gabriel Harnagea Date: Tue, 18 Aug 2026 22:11:40 +0200 Subject: [PATCH 1/2] Add NOMKSTREAM support to XADD Adds a nomkstream option to StreamAdd/StreamAddAsync so callers can append to a stream without implicitly creating it, matching the XADD NOMKSTREAM flag (Redis 6.2+). Existing overloads keep their signatures for binary compatibility; new all-optional overloads carry the flag. Closes #3138. --- docs/Streams.md | 2 + .../Interfaces/IDatabase.cs | 90 +++++++++++++++++- .../Interfaces/IDatabaseAsync.cs | 24 ++++- .../KeyspaceIsolation/KeyPrefixed.cs | 28 +++++- .../KeyspaceIsolation/KeyPrefixedDatabase.cs | 28 +++++- .../PublicAPI/PublicAPI.Shipped.txt | 16 ++-- .../PublicAPI/PublicAPI.Unshipped.txt | 8 ++ src/StackExchange.Redis/RedisDatabase.cs | 91 ++++++++++++++++--- src/StackExchange.Redis/StreamConstants.cs | 2 + .../CommandRetryCategoryUnitTests.cs | 10 +- .../RoundTripUnitTests/StreamAddRoundTrip.cs | 32 +++++++ .../StackExchange.Redis.Tests/StreamTests.cs | 35 +++++++ 12 files changed, 322 insertions(+), 44 deletions(-) create mode 100644 tests/StackExchange.Redis.Tests/RoundTripUnitTests/StreamAddRoundTrip.cs diff --git a/docs/Streams.md b/docs/Streams.md index 47e82c2b9..028221cd4 100644 --- a/docs/Streams.md +++ b/docs/Streams.md @@ -37,6 +37,8 @@ You also have the option to override the auto-generated message ID by passing yo db.StreamAdd("events_stream", "foo_name", "bar_value", messageId: "0-1", maxLength: 100); ``` +Set `nomkstream: true` to append only when the stream already exists; otherwise `StreamAdd` returns a null `RedisValue` without creating the key. + Idempotent write-at-most-once production === diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs index 038439a0b..07bef3008 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabase.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -2778,7 +2778,26 @@ IEnumerable SortedSetScan( /// The ID of the newly created message. /// #pragma warning disable RS0026 // different shape - RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + + /// + /// Adds an entry using the specified values to the given stream key. + /// If key does not exist and is false, 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 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. + /// If true, do not create a stream when the key does not exist. + /// Specifies the maximal count of entries that will be evicted. + /// Determines how stream trimming should be performed. + /// The flags to use for this operation. + /// The ID of the newly created message, or a null value when is true and the key does not exist. + /// + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. @@ -2798,7 +2817,28 @@ IEnumerable SortedSetScan( /// The flags to use for this operation. /// The ID of the newly created message. /// - RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + + /// + /// Adds an entry using the specified values to the given stream key. + /// If key does not exist and is false, a new key holding a stream is created. + /// The command returns the ID of the newly created stream entry, using + /// the idempotent id (pid/iid) mechanism to ensure at-most-once production. + /// See for more information of the idempotent API. + /// + /// The key of the stream. + /// The field name for the stream entry. + /// The value to set in the stream entry. + /// The idempotent producer (pid) and optionally id (iid) to use for this entry. + /// 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. + /// If true, do not create a stream when the key does not exist. + /// Specifies the maximal count of entries that will be evicted. + /// Determines how stream trimming should be performed. + /// The flags to use for this operation. + /// The ID of the newly created message, or a null value when is true and the key does not exist. + /// + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. @@ -2815,7 +2855,27 @@ IEnumerable SortedSetScan( /// The flags to use for this operation. /// The ID of the newly created message. /// - RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + + /// + /// Adds an entry using the specified values to the given stream key. + /// If key does not exist and is false, 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 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. + /// If true, do not create a stream when the key does not exist. + /// Specifies the maximal count of entries that will be evicted. + /// Determines how stream trimming should be performed. + /// The flags to use for this operation. + /// The ID of the newly created message, or a null value when is true and the key does not exist. + /// +#pragma warning disable RS0027 // different shape + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0027 /// /// Adds an entry using the specified values to the given stream key. @@ -2834,7 +2894,29 @@ IEnumerable SortedSetScan( /// The flags to use for this operation. /// The ID of the newly created message. /// - RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + + /// + /// Adds an entry using the specified values to the given stream key. + /// If key does not exist and is false, a new key holding a stream is created. + /// The command returns the ID of the newly created stream entry, using + /// the idempotent id (pid/iid) mechanism to ensure at-most-once production. + /// See for more information of the idempotent API. + /// + /// The key of the stream. + /// The fields and their associated values to set in the stream entry. + /// The idempotent producer (pid) and optionally id (iid) to use for this entry. + /// 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. + /// If true, do not create a stream when the key does not exist. + /// Specifies the maximal count of entries that will be evicted. + /// Determines how stream trimming should be performed. + /// The flags to use for this operation. + /// The ID of the newly created message, or a null value when is true and the key does not exist. + /// +#pragma warning disable RS0027 // different shape + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0027 #pragma warning restore RS0026 /// diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs index f80aa744a..65b25180d 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -688,16 +688,32 @@ IAsyncEnumerable SortedSetScanAsync( #pragma warning disable RS0026 // similar overloads /// - Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + + /// + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + + /// +#pragma warning disable RS0027 // different shape + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0027 /// - Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + + /// + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + + /// +#pragma warning disable RS0027 // different shape + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0027 #pragma warning restore RS0026 /// diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index c92f24d5c..c3c6c040b 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -658,18 +658,38 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamField, Red public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) => Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => Inner.StreamAddAsync(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => +#pragma warning disable RS0026 + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAddAsync(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); +#pragma warning restore RS0026 + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => +#pragma warning disable RS0026 + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); +#pragma warning restore RS0026 + + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => Inner.StreamAddAsync(ToInner(key), streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, limit, mode, flags); - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => +#pragma warning disable RS0026 + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAddAsync(ToInner(key), streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); +#pragma warning restore RS0026 + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => Inner.StreamAddAsync(ToInner(key), streamPairs, idempotentId, maxLength, useApproximateMaxLength, limit, mode, flags); +#pragma warning disable RS0026 + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAddAsync(ToInner(key), streamPairs, idempotentId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); +#pragma warning restore RS0026 + public Task StreamConfigureAsync(RedisKey key, StreamConfiguration configuration, CommandFlags flags = CommandFlags.None) => Inner.StreamConfigureAsync(ToInner(key), configuration, flags); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs index cbf7c72fc..d058be557 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs @@ -625,18 +625,38 @@ public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue str public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) => Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => Inner.StreamAdd(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => +#pragma warning disable RS0026 + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAdd(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); +#pragma warning restore RS0026 + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => +#pragma warning disable RS0026 + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); +#pragma warning restore RS0026 + + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => Inner.StreamAdd(ToInner(key), streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, limit, mode, flags); - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => +#pragma warning disable RS0026 + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAdd(ToInner(key), streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); +#pragma warning restore RS0026 + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => Inner.StreamAdd(ToInner(key), streamPairs, idempotentId, maxLength, useApproximateMaxLength, limit, mode, flags); +#pragma warning disable RS0026 + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAdd(ToInner(key), streamPairs, idempotentId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); +#pragma warning restore RS0026 + public void StreamConfigure(RedisKey key, StreamConfiguration configuration, CommandFlags flags = CommandFlags.None) => Inner.StreamConfigure(ToInner(key), configuration, flags); diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 605f04040..5d6cecaee 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -736,9 +736,9 @@ StackExchange.Redis.IDatabase.StreamAcknowledge(StackExchange.Redis.RedisKey key StackExchange.Redis.IDatabase.StreamAcknowledge(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long StackExchange.Redis.IDatabase.StreamAcknowledgeAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamTrimResult StackExchange.Redis.IDatabase.StreamAcknowledgeAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamTrimResult[]! -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.StreamAutoClaim(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamAutoClaimResult StackExchange.Redis.IDatabase.StreamAutoClaimIdsOnly(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamAutoClaimIdsOnlyResult @@ -982,9 +982,9 @@ StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAsync(StackExchange.Redis.Re StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAutoClaimAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAutoClaimIdsOnlyAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! @@ -2261,13 +2261,13 @@ override StackExchange.Redis.HotKeysResult.MetricKeyCpu.ToString() -> string! override StackExchange.Redis.StreamIdempotentId.Equals(object? obj) -> bool override StackExchange.Redis.StreamIdempotentId.GetHashCode() -> int override StackExchange.Redis.StreamIdempotentId.ToString() -> string! -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.StreamConfigure(StackExchange.Redis.RedisKey key, StackExchange.Redis.StreamConfiguration! configuration, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void StackExchange.Redis.IDatabase.StreamNegativeAcknowledge(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamNackMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long StackExchange.Redis.IDatabase.StreamNegativeAcknowledge(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamNackMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamConfigureAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.StreamConfiguration! configuration, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamNegativeAcknowledgeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamNackMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamNegativeAcknowledgeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamNackMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 295fc5b1f..17ee82297 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -36,3 +36,11 @@ StackExchange.Redis.IServer.ClusterSlots(StackExchange.Redis.CommandFlags flags StackExchange.Redis.IServer.ClusterSlotsAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! [SER007]StackExchange.Redis.RedisErrorKind.UnknownRedirectTarget = 26 -> StackExchange.Redis.RedisErrorKind override StackExchange.Redis.ClusterSlotNode.ToString() -> string! +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 9e4c18ac7..e04fd2c45 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -2892,9 +2892,13 @@ public Task StreamAcknowledgeAndDeleteAsync(RedisKey key, Re } public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) - => StreamAdd(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + => StreamAdd(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: null, mode: StreamTrimMode.KeepReferences, flags: flags); - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) + => StreamAdd(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); + +#pragma warning disable RS0026 + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage( key, @@ -2902,6 +2906,7 @@ public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue str StreamIdempotentId.Empty, maxLength, useApproximateMaxLength, + nomkstream, new NameValueEntry(streamField, streamValue), limit, mode, @@ -2909,8 +2914,13 @@ public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue str return ExecuteSync(msg, ResultProcessor.RedisValue); } +#pragma warning restore RS0026 + + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) + => StreamAdd(key, streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) +#pragma warning disable RS0026 + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage( key, @@ -2918,6 +2928,7 @@ public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue str idempotentId, maxLength, useApproximateMaxLength, + nomkstream, new NameValueEntry(streamField, streamValue), limit, mode, @@ -2925,11 +2936,16 @@ public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue str return ExecuteSync(msg, ResultProcessor.RedisValue); } +#pragma warning restore RS0026 public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) - => StreamAddAsync(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + => StreamAddAsync(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: null, mode: StreamTrimMode.KeepReferences, flags: flags); + + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) + => StreamAddAsync(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) +#pragma warning disable RS0026 + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage( key, @@ -2937,6 +2953,7 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamField, Red StreamIdempotentId.Empty, maxLength, useApproximateMaxLength, + nomkstream, new NameValueEntry(streamField, streamValue), limit, mode, @@ -2944,8 +2961,13 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamField, Red return ExecuteAsync(msg, ResultProcessor.RedisValue); } +#pragma warning restore RS0026 - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) + => StreamAddAsync(key, streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); + +#pragma warning disable RS0026 + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage( key, @@ -2953,6 +2975,7 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamField, Red idempotentId, maxLength, useApproximateMaxLength, + nomkstream, new NameValueEntry(streamField, streamValue), limit, mode, @@ -2960,11 +2983,16 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamField, Red return ExecuteAsync(msg, ResultProcessor.RedisValue); } +#pragma warning restore RS0026 public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) - => StreamAdd(key, streamPairs, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + => StreamAdd(key, streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: null, mode: StreamTrimMode.KeepReferences, flags: flags); + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) + => StreamAdd(key, streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) +#pragma warning disable RS0026 + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage( key, @@ -2972,6 +3000,7 @@ public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisVal StreamIdempotentId.Empty, maxLength, useApproximateMaxLength, + nomkstream, streamPairs, limit, mode, @@ -2979,8 +3008,13 @@ public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisVal return ExecuteSync(msg, ResultProcessor.RedisValue); } +#pragma warning restore RS0026 + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) + => StreamAdd(key, streamPairs, idempotentId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) +#pragma warning disable RS0026 + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage( key, @@ -2988,6 +3022,7 @@ public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamId idempotentId, maxLength, useApproximateMaxLength, + nomkstream, streamPairs, limit, mode, @@ -2995,11 +3030,16 @@ public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamId return ExecuteSync(msg, ResultProcessor.RedisValue); } +#pragma warning restore RS0026 public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) - => StreamAddAsync(key, streamPairs, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + => StreamAddAsync(key, streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: null, mode: StreamTrimMode.KeepReferences, flags: flags); + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) + => StreamAddAsync(key, streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) +#pragma warning disable RS0026 + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage( key, @@ -3007,6 +3047,7 @@ public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPair StreamIdempotentId.Empty, maxLength, useApproximateMaxLength, + nomkstream, streamPairs, limit, mode, @@ -3014,8 +3055,13 @@ public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPair return ExecuteAsync(msg, ResultProcessor.RedisValue); } +#pragma warning restore RS0026 - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) + => StreamAddAsync(key, streamPairs, idempotentId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); + +#pragma warning disable RS0026 + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { var msg = GetStreamAddMessage( key, @@ -3023,6 +3069,7 @@ public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPair idempotentId, maxLength, useApproximateMaxLength, + nomkstream, streamPairs, limit, mode, @@ -3030,6 +3077,7 @@ public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPair return ExecuteAsync(msg, ResultProcessor.RedisValue); } +#pragma warning restore RS0026 public void StreamConfigure(RedisKey key, StreamConfiguration configuration, CommandFlags flags = CommandFlags.None) { @@ -4909,13 +4957,15 @@ private Message GetStreamAcknowledgeAndDeleteMessage(RedisKey key, RedisValue gr return Message.Create(Database, flags, RedisCommand.XACKDEL, key, values); } - internal Message GetStreamAddMessage(in RedisKey key, RedisValue messageId, in StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, NameValueEntry streamPair, long? limit, StreamTrimMode mode, CommandFlags flags) + internal Message GetStreamAddMessage(in RedisKey key, RedisValue messageId, in StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, bool nomkstream, NameValueEntry streamPair, long? limit, StreamTrimMode mode, CommandFlags flags) { // Calculate the correct number of arguments: // 3 array elements for Entry ID & NameValueEntry.Name & NameValueEntry.Value. + // 1 element if using NOMKSTREAM, otherwise 0. // 2 elements if using MAXLEN (keyword & value), otherwise 0. // 1 element if using Approximate Length (~), otherwise 0. - var totalLength = 3 + (maxLength.HasValue ? 2 : 0) + var totalLength = 3 + (nomkstream ? 1 : 0) + + (maxLength.HasValue ? 2 : 0) + idempotentId.ArgCount + (maxLength.HasValue && useApproximateMaxLength ? 1 : 0) + (limit.HasValue ? 2 : 0) @@ -4924,6 +4974,11 @@ internal Message GetStreamAddMessage(in RedisKey key, RedisValue messageId, in S var values = new RedisValue[totalLength]; var offset = 0; + if (nomkstream) + { + values[offset++] = StreamConstants.NoMkStream; + } + if (maxLength.HasValue) { values[offset++] = StreamConstants.MaxLen; @@ -4985,7 +5040,7 @@ internal static bool IsServerAssignedId(in RedisValue messageId) /// /// Gets message for . /// - private Message GetStreamAddMessage(in RedisKey key, RedisValue entryId, in StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, NameValueEntry[] streamPairs, long? limit, StreamTrimMode mode, CommandFlags flags) + private Message GetStreamAddMessage(in RedisKey key, RedisValue entryId, in StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, bool nomkstream, NameValueEntry[] streamPairs, long? limit, StreamTrimMode mode, CommandFlags flags) { if (streamPairs == null) throw new ArgumentNullException(nameof(streamPairs)); if (streamPairs.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPairs), "streamPairs must contain at least one item."); @@ -4998,6 +5053,7 @@ private Message GetStreamAddMessage(in RedisKey key, RedisValue entryId, in Stre var totalLength = (streamPairs.Length * 2) // Room for the name/value pairs + 1 // The stream entry ID + idempotentId.ArgCount + + (nomkstream ? 1 : 0) // NOMKSTREAM + (maxLength.HasValue ? 2 : 0) // MAXLEN N + (maxLength.HasValue && useApproximateMaxLength ? 1 : 0) // ~ + (mode == StreamTrimMode.KeepReferences ? 0 : 1) // relevant trim-mode keyword @@ -5007,6 +5063,11 @@ private Message GetStreamAddMessage(in RedisKey key, RedisValue entryId, in Stre var offset = 0; + if (nomkstream) + { + values[offset++] = StreamConstants.NoMkStream; + } + if (maxLength.HasValue) { values[offset++] = StreamConstants.MaxLen; diff --git a/src/StackExchange.Redis/StreamConstants.cs b/src/StackExchange.Redis/StreamConstants.cs index b63489e01..ba98ef2da 100644 --- a/src/StackExchange.Redis/StreamConstants.cs +++ b/src/StackExchange.Redis/StreamConstants.cs @@ -63,6 +63,8 @@ internal static class StreamConstants internal static readonly RedisValue MkStream = RedisValue.FromRaw("MKSTREAM"u8); + internal static readonly RedisValue NoMkStream = RedisValue.FromRaw("NOMKSTREAM"u8); + internal static readonly RedisValue Stream = RedisValue.FromRaw("STREAM"u8); private static readonly RedisValue KeepRef = RedisValue.FromRaw("KEEPREF"u8), DelRef = RedisValue.FromRaw("DELREF"u8), Acked = RedisValue.FromRaw("ACKED"u8); diff --git a/tests/StackExchange.Redis.Tests/CommandRetryCategoryUnitTests.cs b/tests/StackExchange.Redis.Tests/CommandRetryCategoryUnitTests.cs index 836a00a7b..092910b8c 100644 --- a/tests/StackExchange.Redis.Tests/CommandRetryCategoryUnitTests.cs +++ b/tests/StackExchange.Redis.Tests/CommandRetryCategoryUnitTests.cs @@ -121,27 +121,27 @@ public async Task StreamAdd_ExplicitAndIdempotentIdsAreReplaySafe() // "*" lets the server pick the id, so a replay appends a second entry AssertCategory( Accumulating, - db.GetStreamAddMessage(key, "*", in noId, null, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), + db.GetStreamAddMessage(key, "*", in noId, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), "XADD *"); // an explicit id is rejected second time round ("equal or smaller") AssertCategory( Checked, - db.GetStreamAddMessage(key, "5-5", in noId, null, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), + db.GetStreamAddMessage(key, "5-5", in noId, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), "XADD with explicit id"); // IDMP producer id: the server deduplicates var idmp = new StreamIdempotentId("producer", "item-1"); AssertCategory( Checked, - db.GetStreamAddMessage(key, "*", in idmp, null, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), + db.GetStreamAddMessage(key, "*", in idmp, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), "XADD IDMP"); // IDMPAUTO producer: same, with the id derived from the entry content var idmpAuto = new StreamIdempotentId("producer"); AssertCategory( Checked, - db.GetStreamAddMessage(key, "*", in idmpAuto, null, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), + db.GetStreamAddMessage(key, "*", in idmpAuto, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), "XADD IDMPAUTO"); } @@ -159,7 +159,7 @@ public async Task StreamAdd_PartialAutoIdStillAccumulates() var pair = new NameValueEntry("f", "v"); var noId = default(StreamIdempotentId); - Message Add(RedisValue id) => db.GetStreamAddMessage(key, id, in noId, null, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None); + Message Add(RedisValue id) => db.GetStreamAddMessage(key, id, in noId, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None); // anything the server completes accumulates... AssertCategory(Accumulating, Add("*"), "XADD *"); diff --git a/tests/StackExchange.Redis.Tests/RoundTripUnitTests/StreamAddRoundTrip.cs b/tests/StackExchange.Redis.Tests/RoundTripUnitTests/StreamAddRoundTrip.cs new file mode 100644 index 000000000..a0273289c --- /dev/null +++ b/tests/StackExchange.Redis.Tests/RoundTripUnitTests/StreamAddRoundTrip.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.RoundTripUnitTests; + +public class StreamAddRoundTrip(ITestOutputHelper log) +{ + [Theory(Timeout = 1000)] + [InlineData(false, "*10\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$6\r\nMAXLEN\r\n$1\r\n~\r\n$2\r\n10\r\n$5\r\nLIMIT\r\n$1\r\n5\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n")] + [InlineData(true, "*11\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$10\r\nNOMKSTREAM\r\n$6\r\nMAXLEN\r\n$1\r\n~\r\n$2\r\n10\r\n$5\r\nLIMIT\r\n$1\r\n5\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n")] + public async Task XAddNoMkStream(bool nomkstream, string requestResp) + { + var db = new RedisDatabase(null!, 0, null); + RedisKey key = "stream"; + var pair = new NameValueEntry("field", "value"); + var idempotentId = default(StreamIdempotentId); + var message = db.GetStreamAddMessage( + key, + StreamConstants.AutoGeneratedId, + in idempotentId, + maxLength: 10, + useApproximateMaxLength: true, + nomkstream: nomkstream, + streamPair: pair, + limit: 5, + mode: StreamTrimMode.KeepReferences, + flags: CommandFlags.None); + + var result = await TestConnection.ExecuteAsync(message, ResultProcessor.RedisValue, requestResp, "$3\r\n1-0\r\n", log: log); + Assert.Equal("1-0", result); + } +} diff --git a/tests/StackExchange.Redis.Tests/StreamTests.cs b/tests/StackExchange.Redis.Tests/StreamTests.cs index 514d21763..c2161ca21 100644 --- a/tests/StackExchange.Redis.Tests/StreamTests.cs +++ b/tests/StackExchange.Redis.Tests/StreamTests.cs @@ -81,6 +81,41 @@ public async Task StreamAddWithManualId() Assert.Equal(id, messageId); } + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public async Task StreamAddNoMkStream(bool pairs, bool useAsync) + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + var db = conn.GetDatabase(); + var key = Me() + $":{pairs}:{useAsync}"; + await db.KeyDeleteAsync(key); + + async Task Add(bool nomkstream) + { + if (pairs) + { + NameValueEntry[] fields = [new("field1", "value1"), new("field2", "value2")]; + return useAsync + ? await db.StreamAddAsync(key, fields, nomkstream: nomkstream) + : db.StreamAdd(key, fields, nomkstream: nomkstream); + } + + return useAsync + ? await db.StreamAddAsync(key, "field", "value", nomkstream: nomkstream) + : db.StreamAdd(key, "field", "value", nomkstream: nomkstream); + } + + Assert.Equal(RedisValue.Null, await Add(nomkstream: true)); + Assert.False(await db.KeyExistsAsync(key)); + + Assert.NotEqual(RedisValue.Null, await Add(nomkstream: false)); + Assert.NotEqual(RedisValue.Null, await Add(nomkstream: true)); + Assert.Equal(2, await db.StreamLengthAsync(key)); + } + [Theory] [InlineData(false, false, false)] [InlineData(false, false, true)] From 6a1d9a4269f691a73596b718137751aad374082a Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 20 Aug 2026 14:22:11 +0100 Subject: [PATCH 2/2] XADD: carry NOMKSTREAM (and MINID) on a StreamAddOptions parameter Reworks the public surface: instead of inserting a `bool nomkstream` into the four StreamAdd overload families and their async counterparts, the new options travel on a required `StreamAddOptions` parameter, adding two overloads per interface rather than eight. Because `options` is required it cannot tie with the shipped overloads, so those keep their signatures *and* their default values - PublicAPI.Shipped.txt is untouched. That matters: stripping the defaults broke positional callers, e.g. db.StreamAdd(key, "f", "v", (RedisValue?)"1-1", 5L, true, 10L, StreamTrimMode.DeleteReferences); which no longer bound to either overload once `nomkstream` sat mid-list. [OverloadResolutionPriority] would also have disambiguated, but it is only honoured at LangVersion 13+, so a C# 12 consumer would get CS0121 instead. The flag is spelled `CreateStream` (default true), matching the MKSTREAM parameter we already expose as `StreamCreateConsumerGroup(..., createStream, ...)`, rather than leaking the wire keyword. Since the options type takes future XADD options for free, MINID trimming is included too - previously reachable only through XTRIM - so XADD's grammar is now fully expressible. Notes: - StreamAddOptions is a readonly struct, passed by value publicly and by `in` to the message builders. CreateStream is stored inverted so that `default` means "create the stream", matching the command's own default. - Both builders now share the prefix arithmetic and emission, so NOMKSTREAM, MAXLEN|MINID [~], LIMIT, the trim-mode keyword and the idempotency arguments can only be ordered one way. - The options overloads validate up front (MaxLength xor MinId, MessageId xor IdempotentId, LIMIT requiring ~ and a threshold); the shipped positional overloads deliberately do not, preserving their current behaviour of letting the server rule on odd combinations. --- docs/Streams.md | 15 +- .../Interfaces/IDatabase.cs | 105 ++---- .../Interfaces/IDatabaseAsync.cs | 28 +- .../KeyspaceIsolation/KeyPrefixed.cs | 32 +- .../KeyspaceIsolation/KeyPrefixedDatabase.cs | 32 +- .../PublicAPI/PublicAPI.Shipped.txt | 16 +- .../PublicAPI/PublicAPI.Unshipped.txt | 30 +- src/StackExchange.Redis/RedisDatabase.cs | 335 +++++++----------- src/StackExchange.Redis/StreamAddOptions.cs | 105 ++++++ .../CommandRetryCategoryUnitTests.cs | 16 +- .../KeyPrefixedDatabaseTests.cs | 17 + .../KeyPrefixedTests.cs | 17 + .../RoundTripUnitTests/StreamAddRoundTrip.cs | 139 ++++++-- .../StackExchange.Redis.Tests/StreamTests.cs | 54 ++- 14 files changed, 532 insertions(+), 409 deletions(-) create mode 100644 src/StackExchange.Redis/StreamAddOptions.cs diff --git a/docs/Streams.md b/docs/Streams.md index 028221cd4..0121392d7 100644 --- a/docs/Streams.md +++ b/docs/Streams.md @@ -37,7 +37,20 @@ You also have the option to override the auto-generated message ID by passing yo db.StreamAdd("events_stream", "foo_name", "bar_value", messageId: "0-1", maxLength: 100); ``` -Set `nomkstream: true` to append only when the stream already exists; otherwise `StreamAdd` returns a null `RedisValue` without creating the key. +Trimming, the entry ID and the other `XADD` options can be supplied together via `StreamAddOptions`: + +```csharp +// append only if the stream already exists, trimming anything older than the given entry ID +var id = db.StreamAdd("events_stream", "foo_name", "bar_value", new StreamAddOptions +{ + CreateStream = false, // NOMKSTREAM + MinId = "1526919030474-55", // or MaxLength, for MAXLEN + Approximate = true, // the "~" form, which is cheaper +}); +``` + +With `CreateStream = false`, adding to a stream that does not exist returns `RedisValue.Null` and the key is +not created. `MinId` and `Approximate` require server version 6.2 or above. Idempotent write-at-most-once production === diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs index 07bef3008..141ed8bf4 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabase.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -2778,26 +2778,7 @@ IEnumerable SortedSetScan( /// The ID of the newly created message. /// #pragma warning disable RS0026 // different shape - RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); - - /// - /// Adds an entry using the specified values to the given stream key. - /// If key does not exist and is false, 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 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. - /// If true, do not create a stream when the key does not exist. - /// Specifies the maximal count of entries that will be evicted. - /// Determines how stream trimming should be performed. - /// The flags to use for this operation. - /// The ID of the newly created message, or a null value when is true and the key does not exist. - /// - RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. @@ -2817,28 +2798,7 @@ IEnumerable SortedSetScan( /// The flags to use for this operation. /// The ID of the newly created message. /// - RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); - - /// - /// Adds an entry using the specified values to the given stream key. - /// If key does not exist and is false, a new key holding a stream is created. - /// The command returns the ID of the newly created stream entry, using - /// the idempotent id (pid/iid) mechanism to ensure at-most-once production. - /// See for more information of the idempotent API. - /// - /// The key of the stream. - /// The field name for the stream entry. - /// The value to set in the stream entry. - /// The idempotent producer (pid) and optionally id (iid) to use for this entry. - /// 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. - /// If true, do not create a stream when the key does not exist. - /// Specifies the maximal count of entries that will be evicted. - /// Determines how stream trimming should be performed. - /// The flags to use for this operation. - /// The ID of the newly created message, or a null value when is true and the key does not exist. - /// - RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. @@ -2855,67 +2815,60 @@ IEnumerable SortedSetScan( /// The flags to use for this operation. /// The ID of the newly created message. /// - RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. - /// If key does not exist and is false, a new key holding a stream is created. - /// The command returns the ID of the newly created stream entry. + /// If key does not exist, a new key holding a stream is created. + /// The command returns the ID of the newly created stream entry, using + /// the idempotent id (pid/iid) mechanism to ensure at-most-once production. + /// See for more information of the idempotent API. /// /// The key of the stream. /// The fields and their associated values to set in the stream entry. - /// The ID to assign to the stream entry, defaults to an auto-generated ID ("*"). + /// The idempotent producer (pid) and optionally id (iid) to use for this entry. /// 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. - /// If true, do not create a stream when the key does not exist. /// Specifies the maximal count of entries that will be evicted. /// Determines how stream trimming should be performed. /// The flags to use for this operation. - /// The ID of the newly created message, or a null value when is true and the key does not exist. + /// The ID of the newly created message. /// -#pragma warning disable RS0027 // different shape - RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); -#pragma warning restore RS0027 + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, 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, using - /// the idempotent id (pid/iid) mechanism to ensure at-most-once production. - /// See for more information of the idempotent API. + /// If key does not exist and is set, 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 idempotent producer (pid) and optionally id (iid) to use for this entry. - /// 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. - /// Specifies the maximal count of entries that will be evicted. - /// Determines how stream trimming should be performed. + /// The field name for the stream entry. + /// The value to set in the stream entry. + /// Additional options for this operation, such as trimming and the entry ID. /// The flags to use for this operation. - /// The ID of the newly created message. + /// + /// The ID of the newly created message, or a null value when the key does not exist and + /// is false. + /// /// - RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); +#pragma warning disable RS0027 // additive overload: `options` is required, so existing calls still bind to the overloads above + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamAddOptions options, CommandFlags flags = CommandFlags.None); /// /// Adds an entry using the specified values to the given stream key. - /// If key does not exist and is false, a new key holding a stream is created. - /// The command returns the ID of the newly created stream entry, using - /// the idempotent id (pid/iid) mechanism to ensure at-most-once production. - /// See for more information of the idempotent API. + /// If key does not exist and is set, 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 idempotent producer (pid) and optionally id (iid) to use for this entry. - /// 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. - /// If true, do not create a stream when the key does not exist. - /// Specifies the maximal count of entries that will be evicted. - /// Determines how stream trimming should be performed. + /// Additional options for this operation, such as trimming and the entry ID. /// The flags to use for this operation. - /// The ID of the newly created message, or a null value when is true and the key does not exist. + /// + /// The ID of the newly created message, or a null value when the key does not exist and + /// is false. + /// /// -#pragma warning disable RS0027 // different shape - RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamAddOptions options, CommandFlags flags = CommandFlags.None); #pragma warning restore RS0027 #pragma warning restore RS0026 diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs index 65b25180d..84403ab32 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -688,31 +688,23 @@ IAsyncEnumerable SortedSetScanAsync( #pragma warning disable RS0026 // similar overloads /// - Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); - - /// - Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); - - /// -#pragma warning disable RS0027 // different shape - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); -#pragma warning restore RS0027 + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// - Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); - - /// - Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); /// - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode trimMode, CommandFlags flags); + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + + /// +#pragma warning disable RS0027 // additive overload: `options` is required, so existing calls still bind to the overloads above + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamAddOptions options, CommandFlags flags = CommandFlags.None); - /// -#pragma warning disable RS0027 // different shape - Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + /// + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamAddOptions options, CommandFlags flags = CommandFlags.None); #pragma warning restore RS0027 #pragma warning restore RS0026 diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index c3c6c040b..d2b1a815d 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -658,37 +658,23 @@ public Task StreamAddAsync(RedisKey key, RedisValue streamField, Red public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) => Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => Inner.StreamAddAsync(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); -#pragma warning disable RS0026 - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => - Inner.StreamAddAsync(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); -#pragma warning restore RS0026 - - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); -#pragma warning disable RS0026 - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => - Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); -#pragma warning restore RS0026 - - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => Inner.StreamAddAsync(ToInner(key), streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, limit, mode, flags); -#pragma warning disable RS0026 - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => - Inner.StreamAddAsync(ToInner(key), streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); -#pragma warning restore RS0026 - - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => Inner.StreamAddAsync(ToInner(key), streamPairs, idempotentId, maxLength, useApproximateMaxLength, limit, mode, flags); -#pragma warning disable RS0026 - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => - Inner.StreamAddAsync(ToInner(key), streamPairs, idempotentId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); -#pragma warning restore RS0026 + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamAddOptions options, CommandFlags flags = CommandFlags.None) => + Inner.StreamAddAsync(ToInner(key), streamField, streamValue, options, flags); + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamAddOptions options, CommandFlags flags = CommandFlags.None) => + Inner.StreamAddAsync(ToInner(key), streamPairs, options, flags); public Task StreamConfigureAsync(RedisKey key, StreamConfiguration configuration, CommandFlags flags = CommandFlags.None) => Inner.StreamConfigureAsync(ToInner(key), configuration, flags); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs index d058be557..1ba10ecca 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs @@ -625,37 +625,23 @@ public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue str public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) => Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => Inner.StreamAdd(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); -#pragma warning disable RS0026 - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => - Inner.StreamAdd(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); -#pragma warning restore RS0026 - - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); -#pragma warning disable RS0026 - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => - Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); -#pragma warning restore RS0026 - - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => Inner.StreamAdd(ToInner(key), streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, limit, mode, flags); -#pragma warning disable RS0026 - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => - Inner.StreamAdd(ToInner(key), streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); -#pragma warning restore RS0026 - - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) => + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => Inner.StreamAdd(ToInner(key), streamPairs, idempotentId, maxLength, useApproximateMaxLength, limit, mode, flags); -#pragma warning disable RS0026 - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => - Inner.StreamAdd(ToInner(key), streamPairs, idempotentId, maxLength, useApproximateMaxLength, nomkstream, limit, mode, flags); -#pragma warning restore RS0026 + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamAddOptions options, CommandFlags flags = CommandFlags.None) => + Inner.StreamAdd(ToInner(key), streamField, streamValue, options, flags); + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamAddOptions options, CommandFlags flags = CommandFlags.None) => + Inner.StreamAdd(ToInner(key), streamPairs, options, flags); public void StreamConfigure(RedisKey key, StreamConfiguration configuration, CommandFlags flags = CommandFlags.None) => Inner.StreamConfigure(ToInner(key), configuration, flags); diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 5d6cecaee..605f04040 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -736,9 +736,9 @@ StackExchange.Redis.IDatabase.StreamAcknowledge(StackExchange.Redis.RedisKey key StackExchange.Redis.IDatabase.StreamAcknowledge(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long StackExchange.Redis.IDatabase.StreamAcknowledgeAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamTrimResult StackExchange.Redis.IDatabase.StreamAcknowledgeAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamTrimResult[]! -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.StreamAutoClaim(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamAutoClaimResult StackExchange.Redis.IDatabase.StreamAutoClaimIdsOnly(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamAutoClaimIdsOnlyResult @@ -982,9 +982,9 @@ StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAsync(StackExchange.Redis.Re StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAutoClaimAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamAutoClaimIdsOnlyAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! @@ -2261,13 +2261,13 @@ override StackExchange.Redis.HotKeysResult.MetricKeyCpu.ToString() -> string! override StackExchange.Redis.StreamIdempotentId.Equals(object? obj) -> bool override StackExchange.Redis.StreamIdempotentId.GetHashCode() -> int override StackExchange.Redis.StreamIdempotentId.ToString() -> string! -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.StreamConfigure(StackExchange.Redis.RedisKey key, StackExchange.Redis.StreamConfiguration! configuration, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void StackExchange.Redis.IDatabase.StreamNegativeAcknowledge(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamNackMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long StackExchange.Redis.IDatabase.StreamNegativeAcknowledge(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamNackMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StackExchange.Redis.StreamTrimMode trimMode, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamConfigureAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.StreamConfiguration! configuration, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamNegativeAcknowledgeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamNackMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.StreamNegativeAcknowledgeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamNackMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 17ee82297..3e6df8527 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -36,11 +36,25 @@ StackExchange.Redis.IServer.ClusterSlots(StackExchange.Redis.CommandFlags flags StackExchange.Redis.IServer.ClusterSlotsAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! [SER007]StackExchange.Redis.RedisErrorKind.UnknownRedirectTarget = 26 -> StackExchange.Redis.RedisErrorKind override StackExchange.Redis.ClusterSlotNode.ToString() -> string! -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamAddOptions options, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamAddOptions options, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.StreamAddOptions options, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.StreamAddOptions options, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamAddOptions +StackExchange.Redis.StreamAddOptions.Approximate.get -> bool +StackExchange.Redis.StreamAddOptions.Approximate.init -> void +StackExchange.Redis.StreamAddOptions.CreateStream.get -> bool +StackExchange.Redis.StreamAddOptions.CreateStream.init -> void +StackExchange.Redis.StreamAddOptions.IdempotentId.get -> StackExchange.Redis.StreamIdempotentId +StackExchange.Redis.StreamAddOptions.IdempotentId.init -> void +StackExchange.Redis.StreamAddOptions.Limit.get -> long? +StackExchange.Redis.StreamAddOptions.Limit.init -> void +StackExchange.Redis.StreamAddOptions.MaxLength.get -> long? +StackExchange.Redis.StreamAddOptions.MaxLength.init -> void +StackExchange.Redis.StreamAddOptions.MessageId.get -> StackExchange.Redis.RedisValue? +StackExchange.Redis.StreamAddOptions.MessageId.init -> void +StackExchange.Redis.StreamAddOptions.MinId.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamAddOptions.MinId.init -> void +StackExchange.Redis.StreamAddOptions.StreamAddOptions() -> void +StackExchange.Redis.StreamAddOptions.TrimMode.get -> StackExchange.Redis.StreamTrimMode +StackExchange.Redis.StreamAddOptions.TrimMode.init -> void diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index e04fd2c45..91c99d566 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -2892,192 +2892,100 @@ public Task StreamAcknowledgeAndDeleteAsync(RedisKey key, Re } public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) - => StreamAdd(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: null, mode: StreamTrimMode.KeepReferences, flags: flags); + => StreamAdd(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) - => StreamAdd(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); - -#pragma warning disable RS0026 - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAddMessage( - key, - messageId ?? StreamConstants.AutoGeneratedId, - StreamIdempotentId.Empty, - maxLength, - useApproximateMaxLength, - nomkstream, - new NameValueEntry(streamField, streamValue), - limit, - mode, - flags); - + var options = LegacyStreamAddOptions(messageId, StreamIdempotentId.Empty, maxLength, useApproximateMaxLength, limit, mode); + var msg = GetStreamAddMessage(key, in options, new NameValueEntry(streamField, streamValue), flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } -#pragma warning restore RS0026 - - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) - => StreamAdd(key, streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); -#pragma warning disable RS0026 - public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAddMessage( - key, - StreamConstants.AutoGeneratedId, - idempotentId, - maxLength, - useApproximateMaxLength, - nomkstream, - new NameValueEntry(streamField, streamValue), - limit, - mode, - flags); + var options = LegacyStreamAddOptions(null, in idempotentId, maxLength, useApproximateMaxLength, limit, mode); + var msg = GetStreamAddMessage(key, in options, new NameValueEntry(streamField, streamValue), flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamAddOptions options, CommandFlags flags = CommandFlags.None) + { + options.ThrowIfInvalid(); + var msg = GetStreamAddMessage(key, in options, new NameValueEntry(streamField, streamValue), flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } -#pragma warning restore RS0026 public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) - => StreamAddAsync(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: null, mode: StreamTrimMode.KeepReferences, flags: flags); + => StreamAddAsync(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) - => StreamAddAsync(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); - -#pragma warning disable RS0026 - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAddMessage( - key, - messageId ?? StreamConstants.AutoGeneratedId, - StreamIdempotentId.Empty, - maxLength, - useApproximateMaxLength, - nomkstream, - new NameValueEntry(streamField, streamValue), - limit, - mode, - flags); - + var options = LegacyStreamAddOptions(messageId, StreamIdempotentId.Empty, maxLength, useApproximateMaxLength, limit, mode); + var msg = GetStreamAddMessage(key, in options, new NameValueEntry(streamField, streamValue), flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } -#pragma warning restore RS0026 - - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) - => StreamAddAsync(key, streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); -#pragma warning disable RS0026 - public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAddMessage( - key, - StreamConstants.AutoGeneratedId, - idempotentId, - maxLength, - useApproximateMaxLength, - nomkstream, - new NameValueEntry(streamField, streamValue), - limit, - mode, - flags); + var options = LegacyStreamAddOptions(null, in idempotentId, maxLength, useApproximateMaxLength, limit, mode); + var msg = GetStreamAddMessage(key, in options, new NameValueEntry(streamField, streamValue), flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, StreamAddOptions options, CommandFlags flags = CommandFlags.None) + { + options.ThrowIfInvalid(); + var msg = GetStreamAddMessage(key, in options, new NameValueEntry(streamField, streamValue), flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } -#pragma warning restore RS0026 public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) - => StreamAdd(key, streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: null, mode: StreamTrimMode.KeepReferences, flags: flags); + => StreamAdd(key, streamPairs, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) - => StreamAdd(key, streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); - -#pragma warning disable RS0026 - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAddMessage( - key, - messageId ?? StreamConstants.AutoGeneratedId, - StreamIdempotentId.Empty, - maxLength, - useApproximateMaxLength, - nomkstream, - streamPairs, - limit, - mode, - flags); - + var options = LegacyStreamAddOptions(messageId, StreamIdempotentId.Empty, maxLength, useApproximateMaxLength, limit, mode); + var msg = GetStreamAddMessage(key, in options, streamPairs, flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } -#pragma warning restore RS0026 - - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) - => StreamAdd(key, streamPairs, idempotentId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); -#pragma warning disable RS0026 - public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAddMessage( - key, - StreamConstants.AutoGeneratedId, - idempotentId, - maxLength, - useApproximateMaxLength, - nomkstream, - streamPairs, - limit, - mode, - flags); + var options = LegacyStreamAddOptions(null, in idempotentId, maxLength, useApproximateMaxLength, limit, mode); + var msg = GetStreamAddMessage(key, in options, streamPairs, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, StreamAddOptions options, CommandFlags flags = CommandFlags.None) + { + options.ThrowIfInvalid(); + var msg = GetStreamAddMessage(key, in options, streamPairs, flags); return ExecuteSync(msg, ResultProcessor.RedisValue); } -#pragma warning restore RS0026 public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) - => StreamAddAsync(key, streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: null, mode: StreamTrimMode.KeepReferences, flags: flags); + => StreamAddAsync(key, streamPairs, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) - => StreamAddAsync(key, streamPairs, messageId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); - -#pragma warning disable RS0026 - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAddMessage( - key, - messageId ?? StreamConstants.AutoGeneratedId, - StreamIdempotentId.Empty, - maxLength, - useApproximateMaxLength, - nomkstream, - streamPairs, - limit, - mode, - flags); - + var options = LegacyStreamAddOptions(messageId, StreamIdempotentId.Empty, maxLength, useApproximateMaxLength, limit, mode); + var msg = GetStreamAddMessage(key, in options, streamPairs, flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } -#pragma warning restore RS0026 - - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) - => StreamAddAsync(key, streamPairs, idempotentId, maxLength, useApproximateMaxLength, nomkstream: false, limit: limit, mode: mode, flags: flags); -#pragma warning disable RS0026 - public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, bool nomkstream = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamIdempotentId idempotentId, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) { - var msg = GetStreamAddMessage( - key, - StreamConstants.AutoGeneratedId, - idempotentId, - maxLength, - useApproximateMaxLength, - nomkstream, - streamPairs, - limit, - mode, - flags); + var options = LegacyStreamAddOptions(null, in idempotentId, maxLength, useApproximateMaxLength, limit, mode); + var msg = GetStreamAddMessage(key, in options, streamPairs, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, StreamAddOptions options, CommandFlags flags = CommandFlags.None) + { + options.ThrowIfInvalid(); + var msg = GetStreamAddMessage(key, in options, streamPairs, flags); return ExecuteAsync(msg, ResultProcessor.RedisValue); } -#pragma warning restore RS0026 public void StreamConfigure(RedisKey key, StreamConfiguration configuration, CommandFlags flags = CommandFlags.None) { @@ -4957,60 +4865,92 @@ private Message GetStreamAcknowledgeAndDeleteMessage(RedisKey key, RedisValue gr return Message.Create(Database, flags, RedisCommand.XACKDEL, key, values); } - internal Message GetStreamAddMessage(in RedisKey key, RedisValue messageId, in StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, bool nomkstream, NameValueEntry streamPair, long? limit, StreamTrimMode mode, CommandFlags flags) - { - // Calculate the correct number of arguments: - // 3 array elements for Entry ID & NameValueEntry.Name & NameValueEntry.Value. - // 1 element if using NOMKSTREAM, otherwise 0. - // 2 elements if using MAXLEN (keyword & value), otherwise 0. - // 1 element if using Approximate Length (~), otherwise 0. - var totalLength = 3 + (nomkstream ? 1 : 0) - + (maxLength.HasValue ? 2 : 0) - + idempotentId.ArgCount - + (maxLength.HasValue && useApproximateMaxLength ? 1 : 0) - + (limit.HasValue ? 2 : 0) - + (mode != StreamTrimMode.KeepReferences ? 1 : 0); + /// + /// Maps the legacy positional trim/id arguments onto . + /// + /// + /// Deliberately does *not* call : the shipped overloads + /// have always passed questionable combinations (LIMIT without a threshold, say) through to the + /// server, and that behaviour is preserved; only the options-based overloads validate up-front. + /// + private static StreamAddOptions LegacyStreamAddOptions(RedisValue? messageId, in StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, long? limit, StreamTrimMode mode) + => new() + { + MessageId = messageId, + IdempotentId = idempotentId, + MaxLength = maxLength, + Approximate = useApproximateMaxLength, + Limit = limit, + TrimMode = mode, + }; - var values = new RedisValue[totalLength]; - var offset = 0; + /// + /// The number of arguments written by . + /// + private static int GetStreamAddPrefixLength(in StreamAddOptions options) + => (options.CreateStream ? 0 : 1) // NOMKSTREAM + + (options.HasThreshold ? 2 : 0) // MAXLEN|MINID + + (options.HasThreshold && options.Approximate ? 1 : 0) // ~ + + (options.Limit.HasValue ? 2 : 0) // LIMIT N + + (options.TrimMode == StreamTrimMode.KeepReferences ? 0 : 1) // relevant trim-mode keyword + + options.IdempotentId.ArgCount // IDMP / IDMPAUTO + + 1; // the stream entry ID - if (nomkstream) + /// + /// Writes everything in XADD between the key and the field/value pairs, i.e. + /// [NOMKSTREAM] [MAXLEN|MINID [~] threshold] [LIMIT n] [KEEPREF|DELREF|ACKED] [IDMP...] <*|id>. + /// + private static void WriteStreamAddPrefix(in StreamAddOptions options, RedisValue[] values, ref int offset) + { + if (!options.CreateStream) { values[offset++] = StreamConstants.NoMkStream; } - if (maxLength.HasValue) + if (options.HasThreshold) { - values[offset++] = StreamConstants.MaxLen; + var byMaxLength = options.MaxLength.HasValue; + values[offset++] = byMaxLength ? StreamConstants.MaxLen : StreamConstants.MinId; - if (useApproximateMaxLength) + if (options.Approximate) { values[offset++] = StreamConstants.ApproximateMaxLen; } - values[offset++] = maxLength.Value; + values[offset++] = byMaxLength ? options.MaxLength.GetValueOrDefault() : options.MinId; } - if (limit.HasValue) + if (options.Limit.HasValue) { values[offset++] = RedisLiterals.LIMIT; - values[offset++] = limit.Value; + values[offset++] = options.Limit.GetValueOrDefault(); } - if (mode != StreamTrimMode.KeepReferences) + if (options.TrimMode != StreamTrimMode.KeepReferences) { - values[offset++] = StreamConstants.GetMode(mode); + values[offset++] = StreamConstants.GetMode(options.TrimMode); } - idempotentId.WriteTo(values, ref offset); + options.IdempotentId.WriteTo(values, ref offset); + + values[offset++] = options.EntryId; + } + + internal Message GetStreamAddMessage(in RedisKey key, in StreamAddOptions options, NameValueEntry streamPair, CommandFlags flags) + { + // 2 array elements for NameValueEntry.Name & NameValueEntry.Value, after the shared prefix + var totalLength = 2 + GetStreamAddPrefixLength(in options); + + var values = new RedisValue[totalLength]; + var offset = 0; - values[offset++] = messageId; + WriteStreamAddPrefix(in options, values, ref offset); values[offset++] = streamPair.Name; values[offset++] = streamPair.Value; Debug.Assert(offset == totalLength); - return Message.Create(Database, GetStreamAddCategory(flags, messageId, in idempotentId), RedisCommand.XADD, key, values); + return Message.Create(Database, GetStreamAddCategory(flags, in options), RedisCommand.XADD, key, values); } /// @@ -5024,8 +4964,8 @@ internal Message GetStreamAddMessage(in RedisKey key, RedisValue messageId, in S /// appends a second entry (5-0, then 5-1) rather than being rejected. Testing only against the bare /// * would therefore let a double-append through under the default policy. /// - private static CommandFlags GetStreamAddCategory(CommandFlags flags, in RedisValue messageId, in StreamIdempotentId idempotentId) - => (idempotentId.ArgCount != 0 || !IsServerAssignedId(in messageId)) + private static CommandFlags GetStreamAddCategory(CommandFlags flags, in StreamAddOptions options) + => (options.IdempotentId.ArgCount != 0 || !IsServerAssignedId(options.EntryId)) ? flags.WithCategory(CommandFlags.CommandRetryWriteChecked) : flags; @@ -5040,60 +4980,23 @@ internal static bool IsServerAssignedId(in RedisValue messageId) /// /// Gets message for . /// - private Message GetStreamAddMessage(in RedisKey key, RedisValue entryId, in StreamIdempotentId idempotentId, long? maxLength, bool useApproximateMaxLength, bool nomkstream, NameValueEntry[] streamPairs, long? limit, StreamTrimMode mode, CommandFlags flags) + internal Message GetStreamAddMessage(in RedisKey key, in StreamAddOptions options, NameValueEntry[] streamPairs, CommandFlags flags) { if (streamPairs == null) throw new ArgumentNullException(nameof(streamPairs)); if (streamPairs.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPairs), "streamPairs must contain at least one item."); - if (maxLength.HasValue && maxLength <= 0) + if (options.MaxLength.HasValue && options.MaxLength <= 0) { - throw new ArgumentOutOfRangeException(nameof(maxLength), "maxLength must be greater than 0."); + throw new ArgumentOutOfRangeException(nameof(options), "maxLength must be greater than 0."); } var totalLength = (streamPairs.Length * 2) // Room for the name/value pairs - + 1 // The stream entry ID - + idempotentId.ArgCount - + (nomkstream ? 1 : 0) // NOMKSTREAM - + (maxLength.HasValue ? 2 : 0) // MAXLEN N - + (maxLength.HasValue && useApproximateMaxLength ? 1 : 0) // ~ - + (mode == StreamTrimMode.KeepReferences ? 0 : 1) // relevant trim-mode keyword - + (limit.HasValue ? 2 : 0); // LIMIT N + + GetStreamAddPrefixLength(in options); var values = new RedisValue[totalLength]; - var offset = 0; - if (nomkstream) - { - values[offset++] = StreamConstants.NoMkStream; - } - - if (maxLength.HasValue) - { - values[offset++] = StreamConstants.MaxLen; - - if (useApproximateMaxLength) - { - values[offset++] = StreamConstants.ApproximateMaxLen; - } - - values[offset++] = maxLength.Value; - } - - if (limit.HasValue) - { - values[offset++] = RedisLiterals.LIMIT; - values[offset++] = limit.Value; - } - - if (mode != StreamTrimMode.KeepReferences) - { - values[offset++] = StreamConstants.GetMode(mode); - } - - idempotentId.WriteTo(values, ref offset); - - values[offset++] = entryId; + WriteStreamAddPrefix(in options, values, ref offset); for (var i = 0; i < streamPairs.Length; i++) { @@ -5102,7 +5005,7 @@ private Message GetStreamAddMessage(in RedisKey key, RedisValue entryId, in Stre } Debug.Assert(offset == totalLength); - return Message.Create(Database, GetStreamAddCategory(flags, entryId, in idempotentId), RedisCommand.XADD, key, values); + return Message.Create(Database, GetStreamAddCategory(flags, in options), RedisCommand.XADD, key, values); } internal Message GetStreamAutoClaimMessage(RedisKey key, RedisValue consumerGroup, RedisValue assignToConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count, bool idsOnly, CommandFlags flags) diff --git a/src/StackExchange.Redis/StreamAddOptions.cs b/src/StackExchange.Redis/StreamAddOptions.cs new file mode 100644 index 000000000..7ccec8898 --- /dev/null +++ b/src/StackExchange.Redis/StreamAddOptions.cs @@ -0,0 +1,105 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Additional options for adding an entry to a stream; see . +/// +/// +/// The default value of this type means "append a new entry, letting the server assign the id, creating the +/// stream if it does not already exist, without trimming" - i.e. a plain XADD key * .... +/// +public readonly struct StreamAddOptions +{ + // stored inverted, so that `default` means CreateStream=true; XADD creates the stream unless NOMKSTREAM + // is specified, and the default of this type must match the default of the command + private readonly bool _noMkStream; + + /// + /// The ID to assign to the stream entry; defaults to an auto-generated ID ("*"). + /// + /// Mutually exclusive with . + public RedisValue? MessageId { get; init; } + + /// + /// The idempotent producer (pid) and optionally id (iid) to use for this entry; the server assigns the + /// entry ID in this mode. See for more information of the idempotent API. + /// + /// Mutually exclusive with . + public StreamIdempotentId IdempotentId { get; init; } + + /// + /// Trim the stream to this maximum length (MAXLEN) after appending. + /// + /// Mutually exclusive with . + public long? MaxLength { get; init; } + + /// + /// Trim entries with an ID lower than this (MINID) after appending. + /// + /// Mutually exclusive with . + public RedisValue MinId { get; init; } + + /// + /// If true, the "~" argument is used to allow the stream to exceed the requested threshold by a small + /// number. This improves performance when removing messages. Ignored when no threshold is specified. + /// + public bool Approximate { get; init; } + + /// + /// Specifies the maximal count of entries that will be evicted; requires + /// and a threshold. + /// + public long? Limit { get; init; } + + /// + /// Determines how stream trimming should be performed. + /// + public StreamTrimMode TrimMode { get; init; } + + /// + /// Whether to create the stream when the key does not exist; when false, NOMKSTREAM is used and + /// the command returns a null value instead of creating the stream. Defaults to true. + /// + public bool CreateStream + { + get => !_noMkStream; + init => _noMkStream = !value; + } + + /// + /// Whether a trim threshold (MAXLEN or MINID) has been specified. + /// + internal bool HasThreshold => MaxLength.HasValue || MinId.HasValue; + + /// + /// The entry ID to send; the idempotent forms always let the server assign the ID. + /// + internal RedisValue EntryId => MessageId ?? StreamConstants.AutoGeneratedId; + + internal void ThrowIfInvalid() + { + if (MaxLength.HasValue && MinId.HasValue) + { + Throw($"{nameof(MaxLength)} and {nameof(MinId)} are mutually exclusive."); + } + if (MessageId.HasValue && IdempotentId.ArgCount != 0) + { + Throw($"{nameof(MessageId)} and {nameof(IdempotentId)} are mutually exclusive; the server assigns the entry ID when producing idempotently."); + } + if (Limit.HasValue) + { + // both of these are rejected by the server; fail earlier, and more clearly + if (!HasThreshold) + { + Throw($"{nameof(Limit)} requires {nameof(MaxLength)} or {nameof(MinId)}."); + } + if (!Approximate) + { + Throw($"{nameof(Limit)} requires {nameof(Approximate)}."); + } + } + + static void Throw(string message) => throw new ArgumentException(message, "options"); + } +} diff --git a/tests/StackExchange.Redis.Tests/CommandRetryCategoryUnitTests.cs b/tests/StackExchange.Redis.Tests/CommandRetryCategoryUnitTests.cs index 092910b8c..6ab6341ec 100644 --- a/tests/StackExchange.Redis.Tests/CommandRetryCategoryUnitTests.cs +++ b/tests/StackExchange.Redis.Tests/CommandRetryCategoryUnitTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using Xunit; @@ -110,6 +110,10 @@ public async Task SortedSetAdd_IncrementAccumulates() AssertCategory(Checked, db.GetSortedSetIncrementMessage(key, member, 1.0, ValueCondition.NotExists, CommandFlags.None), "ZADD NX INCR"); } + + private static StreamAddOptions Options(RedisValue messageId, in StreamIdempotentId idempotentId) => + new() { MessageId = messageId, IdempotentId = idempotentId }; + [Fact] public async Task StreamAdd_ExplicitAndIdempotentIdsAreReplaySafe() { @@ -121,27 +125,27 @@ public async Task StreamAdd_ExplicitAndIdempotentIdsAreReplaySafe() // "*" lets the server pick the id, so a replay appends a second entry AssertCategory( Accumulating, - db.GetStreamAddMessage(key, "*", in noId, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), + db.GetStreamAddMessage(key, Options("*", in noId), pair, CommandFlags.None), "XADD *"); // an explicit id is rejected second time round ("equal or smaller") AssertCategory( Checked, - db.GetStreamAddMessage(key, "5-5", in noId, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), + db.GetStreamAddMessage(key, Options("5-5", in noId), pair, CommandFlags.None), "XADD with explicit id"); // IDMP producer id: the server deduplicates var idmp = new StreamIdempotentId("producer", "item-1"); AssertCategory( Checked, - db.GetStreamAddMessage(key, "*", in idmp, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), + db.GetStreamAddMessage(key, Options("*", in idmp), pair, CommandFlags.None), "XADD IDMP"); // IDMPAUTO producer: same, with the id derived from the entry content var idmpAuto = new StreamIdempotentId("producer"); AssertCategory( Checked, - db.GetStreamAddMessage(key, "*", in idmpAuto, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None), + db.GetStreamAddMessage(key, Options("*", in idmpAuto), pair, CommandFlags.None), "XADD IDMPAUTO"); } @@ -159,7 +163,7 @@ public async Task StreamAdd_PartialAutoIdStillAccumulates() var pair = new NameValueEntry("f", "v"); var noId = default(StreamIdempotentId); - Message Add(RedisValue id) => db.GetStreamAddMessage(key, id, in noId, null, false, false, pair, null, StreamTrimMode.KeepReferences, CommandFlags.None); + Message Add(RedisValue id) => db.GetStreamAddMessage(key, Options(id, in noId), pair, CommandFlags.None); // anything the server completes accumulates... AssertCategory(Accumulating, Add("*"), "XADD *"); diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs index a93c425dc..fa7e1fba2 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs @@ -1777,6 +1777,23 @@ public void StreamAdd_WithTrimMode_2() mock.Received().StreamAdd("prefix:key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); } + [Fact] + public void StreamAdd_WithOptions_1() + { + var options = new StreamAddOptions { MaxLength = 1000, CreateStream = false }; + prefixed.StreamAdd("key", "field", "value", options, CommandFlags.None); + mock.Received().StreamAdd("prefix:key", "field", "value", options, CommandFlags.None); + } + + [Fact] + public void StreamAdd_WithOptions_2() + { + var fields = new NameValueEntry[] { new NameValueEntry("field", "value") }; + var options = new StreamAddOptions { MinId = "5-5", CreateStream = false }; + prefixed.StreamAdd("key", fields, options, CommandFlags.None); + mock.Received().StreamAdd("prefix:key", fields, options, CommandFlags.None); + } + [Fact] public void StreamTrim_WithMode() { diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs index 8ab933deb..5df1b380d 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs @@ -1705,6 +1705,23 @@ public async Task StreamAddAsync_WithTrimMode_2() await mock.Received().StreamAddAsync("prefix:key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); } + [Fact] + public async Task StreamAddAsync_WithOptions_1() + { + var options = new StreamAddOptions { MaxLength = 1000, CreateStream = false }; + await prefixed.StreamAddAsync("key", "field", "value", options, CommandFlags.None); + await mock.Received().StreamAddAsync("prefix:key", "field", "value", options, CommandFlags.None); + } + + [Fact] + public async Task StreamAddAsync_WithOptions_2() + { + var fields = new NameValueEntry[] { new NameValueEntry("field", "value") }; + var options = new StreamAddOptions { MinId = "5-5", CreateStream = false }; + await prefixed.StreamAddAsync("key", fields, options, CommandFlags.None); + await mock.Received().StreamAddAsync("prefix:key", fields, options, CommandFlags.None); + } + [Fact] public async Task StreamTrimAsync_WithMode() { diff --git a/tests/StackExchange.Redis.Tests/RoundTripUnitTests/StreamAddRoundTrip.cs b/tests/StackExchange.Redis.Tests/RoundTripUnitTests/StreamAddRoundTrip.cs index a0273289c..13969734e 100644 --- a/tests/StackExchange.Redis.Tests/RoundTripUnitTests/StreamAddRoundTrip.cs +++ b/tests/StackExchange.Redis.Tests/RoundTripUnitTests/StreamAddRoundTrip.cs @@ -1,32 +1,129 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Xunit; namespace StackExchange.Redis.Tests.RoundTripUnitTests; +/// +/// Pins the exact XADD argument order, which the server is strict about: +/// [NOMKSTREAM] [MAXLEN|MINID [~] threshold] [LIMIT n] [KEEPREF|DELREF|ACKED] [IDMP...] <*|id>. +/// public class StreamAddRoundTrip(ITestOutputHelper log) { - [Theory(Timeout = 1000)] - [InlineData(false, "*10\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$6\r\nMAXLEN\r\n$1\r\n~\r\n$2\r\n10\r\n$5\r\nLIMIT\r\n$1\r\n5\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n")] - [InlineData(true, "*11\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$10\r\nNOMKSTREAM\r\n$6\r\nMAXLEN\r\n$1\r\n~\r\n$2\r\n10\r\n$5\r\nLIMIT\r\n$1\r\n5\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n")] - public async Task XAddNoMkStream(bool nomkstream, string requestResp) + private const string Reply = "$3\r\n1-0\r\n"; + + [Fact] + public Task NoOptions() => AssertPairAsync( + default, + "*5\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n"); + + [Fact] + public Task NoMkStream() => AssertPairAsync( + new() { CreateStream = false }, + "*6\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$10\r\nNOMKSTREAM\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n"); + + /// NOMKSTREAM comes before the trim options, not after them. + [Fact] + public Task NoMkStreamPrecedesMaxLen() => AssertPairAsync( + new() { CreateStream = false, MaxLength = 10, Approximate = true, Limit = 5 }, + "*11\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$10\r\nNOMKSTREAM\r\n$6\r\nMAXLEN\r\n$1\r\n~\r\n$2\r\n10\r\n$5\r\nLIMIT\r\n$1\r\n5\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n"); + + [Fact] + public Task MaxLenOnly() => AssertPairAsync( + new() { MaxLength = 10 }, + "*7\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$6\r\nMAXLEN\r\n$2\r\n10\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n"); + + [Fact] + public Task MinIdExact() => AssertPairAsync( + new() { MinId = "1526919030474-55" }, + "*7\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$5\r\nMINID\r\n$16\r\n1526919030474-55\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n"); + + [Fact] + public Task MinIdApproximateWithLimitAndTrimMode() => AssertPairAsync( + new() { MinId = "5-5", Approximate = true, Limit = 3, TrimMode = StreamTrimMode.DeleteReferences }, + "*11\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$5\r\nMINID\r\n$1\r\n~\r\n$3\r\n5-5\r\n$5\r\nLIMIT\r\n$1\r\n3\r\n$6\r\nDELREF\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n"); + + /// An explicit id replaces the "*", and everything else keeps its place. + [Fact] + public Task ExplicitMessageId() => AssertPairAsync( + new() { MessageId = "5-5", CreateStream = false }, + "*6\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$10\r\nNOMKSTREAM\r\n$3\r\n5-5\r\n$5\r\nfield\r\n$5\r\nvalue\r\n"); + + /// The idempotency arguments sit between the trim options and the entry id. + [Fact] + public Task NoMkStreamWithIdempotentId() => AssertPairAsync( + new() { CreateStream = false, IdempotentId = new("producer", "item-1") }, + "*9\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$10\r\nNOMKSTREAM\r\n$4\r\nIDMP\r\n$8\r\nproducer\r\n$6\r\nitem-1\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n"); + + [Fact] + public Task NoMkStreamWithAutoIdempotentIdAndMaxLen() => AssertPairAsync( + new() { CreateStream = false, IdempotentId = new("producer"), MaxLength = 10 }, + "*10\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$10\r\nNOMKSTREAM\r\n$6\r\nMAXLEN\r\n$2\r\n10\r\n$8\r\nIDMPAUTO\r\n$8\r\nproducer\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n"); + + /// The multi-pair builder is a separate code path, and needs its own arithmetic checked. + [Fact] + public async Task PairsArray() { var db = new RedisDatabase(null!, 0, null); - RedisKey key = "stream"; - var pair = new NameValueEntry("field", "value"); - var idempotentId = default(StreamIdempotentId); - var message = db.GetStreamAddMessage( - key, - StreamConstants.AutoGeneratedId, - in idempotentId, - maxLength: 10, - useApproximateMaxLength: true, - nomkstream: nomkstream, - streamPair: pair, - limit: 5, - mode: StreamTrimMode.KeepReferences, - flags: CommandFlags.None); - - var result = await TestConnection.ExecuteAsync(message, ResultProcessor.RedisValue, requestResp, "$3\r\n1-0\r\n", log: log); + NameValueEntry[] pairs = [new("f1", "v1"), new("f2", "v2")]; + var options = new StreamAddOptions { CreateStream = false, MinId = "5-5", Approximate = true }; + var message = db.GetStreamAddMessage("stream", in options, pairs, CommandFlags.None); + + var result = await TestConnection.ExecuteAsync( + message, + ResultProcessor.RedisValue, + "*11\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$10\r\nNOMKSTREAM\r\n$5\r\nMINID\r\n$1\r\n~\r\n$3\r\n5-5\r\n$1\r\n*\r\n$2\r\nf1\r\n$2\r\nv1\r\n$2\r\nf2\r\n$2\r\nv2\r\n", + Reply, + log: log); + Assert.Equal("1-0", result); + } + + /// With NOMKSTREAM and no stream, the server replies nil - which must surface as a null value. + [Fact] + public async Task NilReplyIsNullValue() + { + var db = new RedisDatabase(null!, 0, null); + var options = new StreamAddOptions { CreateStream = false }; + var message = db.GetStreamAddMessage("stream", in options, new NameValueEntry("field", "value"), CommandFlags.None); + + var result = await TestConnection.ExecuteAsync( + message, + ResultProcessor.RedisValue, + "*6\r\n$4\r\nXADD\r\n$6\r\nstream\r\n$10\r\nNOMKSTREAM\r\n$1\r\n*\r\n$5\r\nfield\r\n$5\r\nvalue\r\n", + "$-1\r\n", + log: log); + Assert.True(result.IsNull); + } + + [Theory] + [InlineData(nameof(StreamAddOptions.MaxLength))] + [InlineData(nameof(StreamAddOptions.MessageId))] + [InlineData("LimitWithoutThreshold")] + [InlineData("LimitWithoutApproximate")] + public void InvalidCombinationsAreRejected(string scenario) + { + var db = new RedisDatabase(null!, 0, null); + var options = scenario switch + { + nameof(StreamAddOptions.MaxLength) => new StreamAddOptions { MaxLength = 10, MinId = "5-5" }, + nameof(StreamAddOptions.MessageId) => new StreamAddOptions { MessageId = "5-5", IdempotentId = new("producer") }, + "LimitWithoutThreshold" => new StreamAddOptions { Limit = 5, Approximate = true }, + _ => new StreamAddOptions { MaxLength = 10, Limit = 5 }, + }; + + // the validation is on the public entry-points, not the message builder: the shipped positional + // overloads have always passed odd-but-legal-looking combinations to the server, and still do + var ex = Assert.Throws(() => db.StreamAdd("stream", "field", "value", options)); + log.WriteLine(ex.Message); + Assert.Equal("options", ex.ParamName); + } + + private async Task AssertPairAsync(StreamAddOptions options, string requestResp) + { + var db = new RedisDatabase(null!, 0, null); + var message = db.GetStreamAddMessage("stream", in options, new NameValueEntry("field", "value"), CommandFlags.None); + + var result = await TestConnection.ExecuteAsync(message, ResultProcessor.RedisValue, requestResp, Reply, log: log); Assert.Equal("1-0", result); } } diff --git a/tests/StackExchange.Redis.Tests/StreamTests.cs b/tests/StackExchange.Redis.Tests/StreamTests.cs index c2161ca21..0a62d86ec 100644 --- a/tests/StackExchange.Redis.Tests/StreamTests.cs +++ b/tests/StackExchange.Redis.Tests/StreamTests.cs @@ -86,36 +86,72 @@ public async Task StreamAddWithManualId() [InlineData(false, true)] [InlineData(true, false)] [InlineData(true, true)] - public async Task StreamAddNoMkStream(bool pairs, bool useAsync) + public async Task StreamAddCreateStreamFalse(bool pairs, bool useAsync) { await using var conn = Create(require: RedisFeatures.v6_2_0); var db = conn.GetDatabase(); var key = Me() + $":{pairs}:{useAsync}"; await db.KeyDeleteAsync(key); - async Task Add(bool nomkstream) + async Task Add(bool createStream) { + var options = new StreamAddOptions { CreateStream = createStream }; if (pairs) { NameValueEntry[] fields = [new("field1", "value1"), new("field2", "value2")]; return useAsync - ? await db.StreamAddAsync(key, fields, nomkstream: nomkstream) - : db.StreamAdd(key, fields, nomkstream: nomkstream); + ? await db.StreamAddAsync(key, fields, options) + : db.StreamAdd(key, fields, options); } return useAsync - ? await db.StreamAddAsync(key, "field", "value", nomkstream: nomkstream) - : db.StreamAdd(key, "field", "value", nomkstream: nomkstream); + ? await db.StreamAddAsync(key, "field", "value", options) + : db.StreamAdd(key, "field", "value", options); } - Assert.Equal(RedisValue.Null, await Add(nomkstream: true)); + // no stream, and we declined to create one: nothing happens, and we are told so + Assert.Equal(RedisValue.Null, await Add(createStream: false)); Assert.False(await db.KeyExistsAsync(key)); - Assert.NotEqual(RedisValue.Null, await Add(nomkstream: false)); - Assert.NotEqual(RedisValue.Null, await Add(nomkstream: true)); + // ...but once the stream exists, the same call appends as normal + Assert.NotEqual(RedisValue.Null, await Add(createStream: true)); + Assert.NotEqual(RedisValue.Null, await Add(createStream: false)); Assert.Equal(2, await db.StreamLengthAsync(key)); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task StreamAddTrimsByMinId(bool approximate) + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + var db = conn.GetDatabase(); + var key = Me() + $":{approximate}"; + await db.KeyDeleteAsync(key); + + for (var i = 1; i <= 5; i++) + { + db.StreamAdd(key, "f", i, new StreamAddOptions { MessageId = $"{i}-1" }); + } + Assert.Equal(5, await db.StreamLengthAsync(key)); + + // exact MINID must drop everything below the threshold; the approximate form is + // free to keep more, so only assert what the server guarantees in each mode + db.StreamAdd(key, "f", 6, new StreamAddOptions { MessageId = "6-1", MinId = "4-1", Approximate = approximate }); + + var entries = await db.StreamRangeAsync(key); + Assert.Contains(entries, x => x.Id == "6-1"); + if (approximate) + { + Assert.True(entries.Length <= 6); + } + else + { + Assert.Equal(3, entries.Length); // 4-1, 5-1, 6-1 + Assert.DoesNotContain(entries, x => x.Id == "3-1"); + } + } + [Theory] [InlineData(false, false, false)] [InlineData(false, false, true)]