diff --git a/Directory.Packages.props b/Directory.Packages.props index 3fa9e0e3d..d704be4ce 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -29,6 +29,7 @@ + diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index c0021f024..1c32dab48 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -40,6 +40,12 @@ public static int ParseInt32(string key, string value, int minValue = int.MinVal return tmp; } + public static float ParseSingle(string key, string value) + { + if (!Format.TryParseDouble(value, out double tmp)) throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' requires a numeric value; the value '{value}' is not recognised."); + return (float)tmp; + } + internal static bool ParseBoolean(string key, string value) { if (!Format.TryParseBoolean(value, out bool tmp)) throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' requires a boolean value; the value '{value}' is not recognised."); @@ -940,9 +946,9 @@ public string ToString(bool includePassword) }; } - private static void Append(StringBuilder sb, object value) + private static void Append(StringBuilder sb, object? value) { - if (value == null) return; + if (value is null) return; string s = Format.ToString(value); if (!string.IsNullOrWhiteSpace(s)) { @@ -953,7 +959,8 @@ private static void Append(StringBuilder sb, object value) private static void Append(StringBuilder sb, string prefix, object? value) { - string? s = value?.ToString(); + if (value is null) return; + string? s = value.ToString(); if (!string.IsNullOrWhiteSpace(s)) { if (sb.Length != 0) sb.Append(','); diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs index e19de6d52..cd08a2cb3 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs @@ -1036,7 +1036,7 @@ public void UnRoot(int token) } } - private void OnHeartbeat() + internal void OnHeartbeat() { try { @@ -1129,7 +1129,7 @@ public IDatabase GetDatabase(int db = -1, object? asyncState = null) } // DB zero is stored separately, since 0-only is a massively common use-case - private const int MaxCachedDatabaseInstance = 16; // 17 items - [0,16] + internal const int MaxCachedDatabaseInstance = 16; // 17 items - [0,16] // Side note: "databases 16" is the default in redis.conf; happy to store one extra to get nice alignment etc private IDatabase? dbCacheZero; private IDatabase[]? dbCacheLow; @@ -1282,6 +1282,8 @@ public long OperationCount } } + internal uint LatencyTicks { get; private set; } = uint.MaxValue; + // note that the RedisChannel->byte[] converter is always direct, so this is not an alloc // (we deal with channels far less frequently, so pay the encoding cost up-front) internal byte[] ChannelPrefix => ((byte[]?)RawConfig.ChannelPrefix) ?? []; @@ -2359,5 +2361,29 @@ private Task[] QuitAllServers() long? IInternalConnectionMultiplexer.GetConnectionId(EndPoint endpoint, ConnectionType type) => GetServerEndPoint(endpoint)?.GetBridge(type)?.ConnectionId; + + internal uint UpdateLatency() + { + var snapshot = GetServerSnapshot(); + uint max = uint.MaxValue; + foreach (var server in snapshot) + { + if (server.IsConnected) + { + var latency = server.LatencyTicks; + if (max is uint.MaxValue || latency > max) + { + max = latency; + } + } + } + + if (max != uint.MaxValue) + { + LatencyTicks = max; + } + + return LatencyTicks; + } } } diff --git a/src/StackExchange.Redis/Interfaces/IConnectionGroup.cs b/src/StackExchange.Redis/Interfaces/IConnectionGroup.cs new file mode 100644 index 000000000..a066e5a32 --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IConnectionGroup.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis; + +/// +/// A group of connections to redis servers, that manages connections to multiple +/// servers, routing traffic based on the availability of the servers and their +/// relative . +/// +public interface IConnectionGroup : IConnectionMultiplexer +{ + /// + /// A change occured to one of the connection groups. + /// + event EventHandler? ConnectionChanged; + + /// + /// Adds a new member to the group. + /// + Task AddAsync(ConnectionGroupMember group, TextWriter? log = null); + + /// + /// Removes a member from the group. + /// + bool Remove(ConnectionGroupMember group); + + /// + /// Get the members of the group. + /// + ReadOnlySpan GetMembers(); +} + +/// +/// Represents a change to a connection group. +/// +public class GroupConnectionChangedEventArgs(GroupConnectionChangedEventArgs.ChangeType type, ConnectionGroupMember group, ConnectionGroupMember? previousGroup = null) : EventArgs, ICompletable +{ + /// + /// The group relating to the change. For , this is the new group. + /// + public ConnectionGroupMember Group => group; + + /// + /// The previous group relating to the change, if applicable. + /// + public ConnectionGroupMember? PreviousGroup => previousGroup; + + /// + /// The type of change that occurred. + /// + public ChangeType Type => type; + + private EventHandler? _handler; + private object? _sender; + + /// + /// The type of change that occurred. + /// + public enum ChangeType + { + /// + /// Unused. + /// + Unknown = 0, + + /// + /// A new connection group was added. + /// + Added = 1, + + /// + /// A connection group was removed. + /// + Removed = 2, + + /// + /// A connection group became disconnected. + /// + Disconnected = 3, + + /// + /// A connection group became reconnected. + /// + Reconnected = 4, + + /// + /// The active connection group changed, changing how traffic is routed. + /// + ActiveChanged = 5, + } + + internal void CompleteAsWorker(EventHandler handler, object sender) + { + _handler = handler; + _sender = sender; + ConnectionMultiplexer.CompleteAsWorker(this); + } + + void ICompletable.AppendStormLog(StringBuilder sb) { } + + bool ICompletable.TryComplete(bool isAsync) => ConnectionMultiplexer.TryCompleteHandler(_handler, _sender!, this, isAsync); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.Async.cs new file mode 100644 index 000000000..03706f355 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Async.cs @@ -0,0 +1,66 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Async methods - Core operations + public Task DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().DebugObjectAsync(key, flags); + + public Task IdentifyEndpointAsync(RedisKey key = default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().IdentifyEndpointAsync(key, flags); + + public Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyMigrateAsync(key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); + + public Task PingAsync(CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().PingAsync(flags); + + public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().PublishAsync(channel, message, flags); + + public Task ExecuteAsync(string command, params object[] args) + => GetActiveDatabase().ExecuteAsync(command, args); + + public Task ExecuteAsync(string command, System.Collections.Generic.ICollection? args, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ExecuteAsync(command, args, flags); + + public Task ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluateAsync(script, keys, values, flags); + + public Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluateAsync(hash, keys, values, flags); + + public Task ScriptEvaluateAsync(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluateAsync(script, parameters, flags); + + public Task ScriptEvaluateAsync(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluateAsync(script, parameters, flags); + + public Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluateReadOnlyAsync(script, keys, values, flags); + + public Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluateReadOnlyAsync(hash, keys, values, flags); + + public Task LockExtendAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().LockExtendAsync(key, value, expiry, flags); + + public Task LockQueryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().LockQueryAsync(key, flags); + + public Task LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().LockReleaseAsync(key, value, flags); + + public Task LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().LockTakeAsync(key, value, expiry, flags); + + public Task SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortAsync(key, skip, take, order, sortType, by, get, flags); + + public Task SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortAndStoreAsync(destination, key, skip, take, order, sortType, by, get, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Geo.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.Geo.Async.cs new file mode 100644 index 000000000..422ff59fc --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Geo.Async.cs @@ -0,0 +1,52 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Geo Async + public Task GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoAddAsync(key, longitude, latitude, member, flags); + + public Task GeoAddAsync(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoAddAsync(key, value, flags); + + public Task GeoAddAsync(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoAddAsync(key, values, flags); + + public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoRemoveAsync(key, member, flags); + + public Task GeoDistanceAsync(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoDistanceAsync(key, member1, member2, unit, flags); + + public Task GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoHashAsync(key, members, flags); + + public Task GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoHashAsync(key, member, flags); + + public Task GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoPositionAsync(key, members, flags); + + public Task GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoPositionAsync(key, member, flags); + + public Task GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoRadiusAsync(key, member, radius, unit, count, order, options, flags); + + public Task GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoRadiusAsync(key, longitude, latitude, radius, unit, count, order, options, flags); + + public Task GeoSearchAsync(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoSearchAsync(key, member, shape, count, demandClosest, order, options, flags); + + public Task GeoSearchAsync(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoSearchAsync(key, longitude, latitude, shape, count, demandClosest, order, options, flags); + + public Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoSearchAndStoreAsync(sourceKey, destinationKey, member, shape, count, demandClosest, order, storeDistances, flags); + + public Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoSearchAndStoreAsync(sourceKey, destinationKey, longitude, latitude, shape, count, demandClosest, order, storeDistances, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Geo.cs b/src/StackExchange.Redis/MultiGroupDatabase.Geo.cs new file mode 100644 index 000000000..c74982c83 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Geo.cs @@ -0,0 +1,50 @@ +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Geo operations + public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoAdd(key, longitude, latitude, member, flags); + + public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoAdd(key, value, flags); + + public long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoAdd(key, values, flags); + + public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoRemove(key, member, flags); + + public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoDistance(key, member1, member2, unit, flags); + + public string?[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoHash(key, members, flags); + + public string? GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoHash(key, member, flags); + + public GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoPosition(key, members, flags); + + public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoPosition(key, member, flags); + + public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoRadius(key, member, radius, unit, count, order, options, flags); + + public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoRadius(key, longitude, latitude, radius, unit, count, order, options, flags); + + public GeoRadiusResult[] GeoSearch(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoSearch(key, member, shape, count, demandClosest, order, options, flags); + + public GeoRadiusResult[] GeoSearch(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoSearch(key, longitude, latitude, shape, count, demandClosest, order, options, flags); + + public long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoSearchAndStore(sourceKey, destinationKey, member, shape, count, demandClosest, order, storeDistances, flags); + + public long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().GeoSearchAndStore(sourceKey, destinationKey, longitude, latitude, shape, count, demandClosest, order, storeDistances, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Hashes.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.Hashes.Async.cs new file mode 100644 index 000000000..15e8111e8 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Hashes.Async.cs @@ -0,0 +1,128 @@ +using System; +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Hash Async + public Task HashDecrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashDecrementAsync(key, hashField, value, flags); + + public Task HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashDecrementAsync(key, hashField, value, flags); + + public Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashDeleteAsync(key, hashField, flags); + + public Task HashDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashDeleteAsync(key, hashFields, flags); + + public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashExistsAsync(key, hashField, flags); + + public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldExpireAsync(key, hashFields, expiry, when, flags); + + public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldExpireAsync(key, hashFields, expiry, when, flags); + + public Task HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetExpireDateTimeAsync(key, hashFields, flags); + + public Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldPersistAsync(key, hashFields, flags); + + public Task HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetTimeToLiveAsync(key, hashFields, flags); + + public Task HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashGetAsync(key, hashField, flags); + + public Task?> HashGetLeaseAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashGetLeaseAsync(key, hashField, flags); + + public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashGetAsync(key, hashFields, flags); + + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndDeleteAsync(key, hashField, flags); + + public Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetLeaseAndDeleteAsync(key, hashField, flags); + + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndDeleteAsync(key, hashFields, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndSetExpiryAsync(key, hashField, expiry, persist, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndSetExpiryAsync(key, hashField, expiry, flags); + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, persist, flags); + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetLeaseAndSetExpiryAsync(key, hashField, expiry, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, persist, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndSetExpiryAsync(key, hashFields, expiry, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldSetAndSetExpiryAsync(key, field, value, expiry, keepTtl, when, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldSetAndSetExpiryAsync(key, field, value, expiry, when, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, keepTtl, when, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldSetAndSetExpiryAsync(key, hashFields, expiry, when, flags); + + public Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashGetAllAsync(key, flags); + + public Task HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashIncrementAsync(key, hashField, value, flags); + + public Task HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashIncrementAsync(key, hashField, value, flags); + + public Task HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashKeysAsync(key, flags); + + public Task HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashLengthAsync(key, flags); + + public Task HashRandomFieldAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashRandomFieldAsync(key, flags); + + public Task HashRandomFieldsAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashRandomFieldsAsync(key, count, flags); + + public Task HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashRandomFieldsWithValuesAsync(key, count, flags); + + public System.Collections.Generic.IAsyncEnumerable HashScanAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashScanAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + public System.Collections.Generic.IAsyncEnumerable HashScanNoValuesAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashScanNoValuesAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + public Task HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashSetAsync(key, hashField, value, when, flags); + + public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashSetAsync(key, hashFields, flags); + + public Task HashStringLengthAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashStringLengthAsync(key, hashField, flags); + + public Task HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashValuesAsync(key, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Hashes.cs b/src/StackExchange.Redis/MultiGroupDatabase.Hashes.cs new file mode 100644 index 000000000..312a4dcb0 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Hashes.cs @@ -0,0 +1,130 @@ +using System; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Hash operations + public long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashDecrement(key, hashField, value, flags); + + public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashDecrement(key, hashField, value, flags); + + public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashDelete(key, hashField, flags); + + public long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashDelete(key, hashFields, flags); + + public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashExists(key, hashField, flags); + + public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldExpire(key, hashFields, expiry, when, flags); + + public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldExpire(key, hashFields, expiry, when, flags); + + public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetExpireDateTime(key, hashFields, flags); + + public PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldPersist(key, hashFields, flags); + + public long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetTimeToLive(key, hashFields, flags); + + public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashGet(key, hashField, flags); + + public Lease? HashGetLease(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashGetLease(key, hashField, flags); + + public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashGet(key, hashFields, flags); + + public RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndDelete(key, hashField, flags); + + public Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetLeaseAndDelete(key, hashField, flags); + + public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndDelete(key, hashFields, flags); + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndSetExpiry(key, hashField, expiry, persist, flags); + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndSetExpiry(key, hashField, expiry, flags); + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, persist, flags); + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetLeaseAndSetExpiry(key, hashField, expiry, flags); + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndSetExpiry(key, hashFields, expiry, persist, flags); + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldGetAndSetExpiry(key, hashFields, expiry, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldSetAndSetExpiry(key, field, value, expiry, keepTtl, when, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldSetAndSetExpiry(key, field, value, expiry, when, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldSetAndSetExpiry(key, hashFields, expiry, keepTtl, when, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashFieldSetAndSetExpiry(key, hashFields, expiry, when, flags); + + public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashGetAll(key, flags); + + public long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashIncrement(key, hashField, value, flags); + + public double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashIncrement(key, hashField, value, flags); + + public RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashKeys(key, flags); + + public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashLength(key, flags); + + public RedisValue HashRandomField(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashRandomField(key, flags); + + public RedisValue[] HashRandomFields(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashRandomFields(key, count, flags); + + public HashEntry[] HashRandomFieldsWithValues(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashRandomFieldsWithValues(key, count, flags); + + public System.Collections.Generic.IEnumerable HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) + => GetActiveDatabase().HashScan(key, pattern, pageSize, flags); + + public System.Collections.Generic.IEnumerable HashScan(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashScan(key, pattern, pageSize, cursor, pageOffset, flags); + + public System.Collections.Generic.IEnumerable HashScanNoValues(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashScanNoValues(key, pattern, pageSize, cursor, pageOffset, flags); + + public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashSet(key, hashField, value, when, flags); + + public void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashSet(key, hashFields, flags); + + public long HashStringLength(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashStringLength(key, hashField, flags); + + public RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HashValues(key, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.HyperLogLog.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.HyperLogLog.Async.cs new file mode 100644 index 000000000..d57166885 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.HyperLogLog.Async.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // HyperLogLog Async + public Task HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogAddAsync(key, value, flags); + + public Task HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogAddAsync(key, values, flags); + + public Task HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogLengthAsync(key, flags); + + public Task HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogLengthAsync(keys, flags); + + public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogMergeAsync(destination, first, second, flags); + + public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogMergeAsync(destination, sourceKeys, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.HyperLogLog.cs b/src/StackExchange.Redis/MultiGroupDatabase.HyperLogLog.cs new file mode 100644 index 000000000..3312b4fc0 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.HyperLogLog.cs @@ -0,0 +1,23 @@ +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // HyperLogLog operations + public bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogAdd(key, value, flags); + + public bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogAdd(key, values, flags); + + public long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogLength(key, flags); + + public long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogLength(keys, flags); + + public void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogMerge(destination, first, second, flags); + + public void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().HyperLogLogMerge(destination, sourceKeys, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Keys.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.Keys.Async.cs new file mode 100644 index 000000000..76c7798be --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Keys.Async.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Key Async + public Task KeyCopyAsync(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyCopyAsync(sourceKey, destinationKey, destinationDatabase, replace, flags); + + public Task KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyDeleteAsync(key, flags); + + public Task KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyDeleteAsync(keys, flags); + + public Task KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyDumpAsync(key, flags); + + public Task KeyEncodingAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyEncodingAsync(key, flags); + + public Task KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExistsAsync(key, flags); + + public Task KeyExistsAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExistsAsync(keys, flags); + + public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags) + => GetActiveDatabase().KeyExpireAsync(key, expiry, flags); + + public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExpireAsync(key, expiry, when, flags); + + public Task KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags) + => GetActiveDatabase().KeyExpireAsync(key, expiry, flags); + + public Task KeyExpireAsync(RedisKey key, DateTime? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExpireAsync(key, expiry, when, flags); + + public Task KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExpireTimeAsync(key, flags); + + public Task KeyFrequencyAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyFrequencyAsync(key, flags); + + public Task KeyIdleTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyIdleTimeAsync(key, flags); + + public Task KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyMoveAsync(key, database, flags); + + public Task KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyPersistAsync(key, flags); + + public Task KeyRandomAsync(CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyRandomAsync(flags); + + public Task KeyRefCountAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyRefCountAsync(key, flags); + + public Task KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyRenameAsync(key, newKey, when, flags); + + public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyRestoreAsync(key, value, expiry, flags); + + public Task KeyTimeToLiveAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyTimeToLiveAsync(key, flags); + + public Task KeyTouchAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyTouchAsync(key, flags); + + public Task KeyTouchAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyTouchAsync(keys, flags); + + public Task KeyTypeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyTypeAsync(key, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Keys.cs b/src/StackExchange.Redis/MultiGroupDatabase.Keys.cs new file mode 100644 index 000000000..2ee6bdce0 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Keys.cs @@ -0,0 +1,79 @@ +using System; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Key operations + public bool KeyCopy(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyCopy(sourceKey, destinationKey, destinationDatabase, replace, flags); + + public bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyDelete(key, flags); + + public long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyDelete(keys, flags); + + public byte[]? KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyDump(key, flags); + + public string? KeyEncoding(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyEncoding(key, flags); + + public bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExists(key, flags); + + public long KeyExists(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExists(keys, flags); + + public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags) + => GetActiveDatabase().KeyExpire(key, expiry, flags); + + public bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExpire(key, expiry, when, flags); + + public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags) + => GetActiveDatabase().KeyExpire(key, expiry, flags); + + public bool KeyExpire(RedisKey key, DateTime? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExpire(key, expiry, when, flags); + + public DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyExpireTime(key, flags); + + public long? KeyFrequency(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyFrequency(key, flags); + + public TimeSpan? KeyIdleTime(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyIdleTime(key, flags); + + public bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyMove(key, database, flags); + + public bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyPersist(key, flags); + + public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyRandom(flags); + + public long? KeyRefCount(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyRefCount(key, flags); + + public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyRename(key, newKey, when, flags); + + public void KeyRestore(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyRestore(key, value, expiry, flags); + + public TimeSpan? KeyTimeToLive(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyTimeToLive(key, flags); + + public bool KeyTouch(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyTouch(key, flags); + + public long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyTouch(keys, flags); + + public RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyType(key, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Lists.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.Lists.Async.cs new file mode 100644 index 000000000..d284dfee9 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Lists.Async.cs @@ -0,0 +1,79 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // List Async operations + public Task ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListGetByIndexAsync(key, index, flags); + + public Task ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListInsertAfterAsync(key, pivot, value, flags); + + public Task ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListInsertBeforeAsync(key, pivot, value, flags); + + public Task ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPopAsync(key, flags); + + public Task ListLeftPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPopAsync(key, count, flags); + + public Task ListLeftPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPopAsync(keys, count, flags); + + public Task ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPushAsync(key, value, when, flags); + + public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPushAsync(key, values, when, flags); + + public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags) + => GetActiveDatabase().ListLeftPushAsync(key, values, flags); + + public Task ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLengthAsync(key, flags); + + public Task ListMoveAsync(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListMoveAsync(sourceKey, destinationKey, sourceSide, destinationSide, flags); + + public Task ListPositionAsync(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListPositionAsync(key, element, rank, maxLength, flags); + + public Task ListPositionsAsync(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListPositionsAsync(key, element, count, rank, maxLength, flags); + + public Task ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRangeAsync(key, start, stop, flags); + + public Task ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRemoveAsync(key, value, count, flags); + + public Task ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPopAsync(key, flags); + + public Task ListRightPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPopAsync(key, count, flags); + + public Task ListRightPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPopAsync(keys, count, flags); + + public Task ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPopLeftPushAsync(source, destination, flags); + + public Task ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPushAsync(key, value, when, flags); + + public Task ListRightPushAsync(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPushAsync(key, values, when, flags); + + public Task ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags) + => GetActiveDatabase().ListRightPushAsync(key, values, flags); + + public Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListSetByIndexAsync(key, index, value, flags); + + public Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListTrimAsync(key, start, stop, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Lists.cs b/src/StackExchange.Redis/MultiGroupDatabase.Lists.cs new file mode 100644 index 000000000..58ffc7a1b --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Lists.cs @@ -0,0 +1,77 @@ +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // List operations + public RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListGetByIndex(key, index, flags); + + public long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListInsertAfter(key, pivot, value, flags); + + public long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListInsertBefore(key, pivot, value, flags); + + public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPop(key, flags); + + public RedisValue[] ListLeftPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPop(key, count, flags); + + public ListPopResult ListLeftPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPop(keys, count, flags); + + public long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPush(key, value, when, flags); + + public long ListLeftPush(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLeftPush(key, values, when, flags); + + public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags) + => GetActiveDatabase().ListLeftPush(key, values, flags); + + public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListLength(key, flags); + + public RedisValue ListMove(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListMove(sourceKey, destinationKey, sourceSide, destinationSide, flags); + + public long ListPosition(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListPosition(key, element, rank, maxLength, flags); + + public long[] ListPositions(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListPositions(key, element, count, rank, maxLength, flags); + + public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRange(key, start, stop, flags); + + public long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRemove(key, value, count, flags); + + public RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPop(key, flags); + + public RedisValue[] ListRightPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPop(key, count, flags); + + public ListPopResult ListRightPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPop(keys, count, flags); + + public RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPopLeftPush(source, destination, flags); + + public long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPush(key, value, when, flags); + + public long ListRightPush(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListRightPush(key, values, when, flags); + + public long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags) + => GetActiveDatabase().ListRightPush(key, values, flags); + + public void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListSetByIndex(key, index, value, flags); + + public void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ListTrim(key, start, stop, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Sets.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.Sets.Async.cs new file mode 100644 index 000000000..7d8eddf54 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Sets.Async.cs @@ -0,0 +1,64 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Set Async operations + public Task SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetAddAsync(key, value, flags); + + public Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetAddAsync(key, values, flags); + + public Task SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetCombineAsync(operation, first, second, flags); + + public Task SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetCombineAsync(operation, keys, flags); + + public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetCombineAndStoreAsync(operation, destination, first, second, flags); + + public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetCombineAndStoreAsync(operation, destination, keys, flags); + + public Task SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetContainsAsync(key, value, flags); + + public Task SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetContainsAsync(key, values, flags); + + public Task SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetIntersectionLengthAsync(keys, limit, flags); + + public Task SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetLengthAsync(key, flags); + + public Task SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetMembersAsync(key, flags); + + public Task SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetMoveAsync(source, destination, value, flags); + + public Task SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetPopAsync(key, flags); + + public Task SetPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetPopAsync(key, count, flags); + + public Task SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetRandomMemberAsync(key, flags); + + public Task SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetRandomMembersAsync(key, count, flags); + + public Task SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetRemoveAsync(key, value, flags); + + public Task SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetRemoveAsync(key, values, flags); + + public System.Collections.Generic.IAsyncEnumerable SetScanAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetScanAsync(key, pattern, pageSize, cursor, pageOffset, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Sets.cs b/src/StackExchange.Redis/MultiGroupDatabase.Sets.cs new file mode 100644 index 000000000..f04df3eeb --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Sets.cs @@ -0,0 +1,65 @@ +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Set operations + public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetAdd(key, value, flags); + + public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetAdd(key, values, flags); + + public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetCombine(operation, first, second, flags); + + public RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetCombine(operation, keys, flags); + + public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetCombineAndStore(operation, destination, first, second, flags); + + public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetCombineAndStore(operation, destination, keys, flags); + + public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetContains(key, value, flags); + + public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetContains(key, values, flags); + + public long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetIntersectionLength(keys, limit, flags); + + public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetLength(key, flags); + + public RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetMembers(key, flags); + + public bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetMove(source, destination, value, flags); + + public RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetPop(key, flags); + + public RedisValue[] SetPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetPop(key, count, flags); + + public RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetRandomMember(key, flags); + + public RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetRandomMembers(key, count, flags); + + public bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetRemove(key, value, flags); + + public long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetRemove(key, values, flags); + + public System.Collections.Generic.IEnumerable SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) + => GetActiveDatabase().SetScan(key, pattern, pageSize, flags); + + public System.Collections.Generic.IEnumerable SetScan(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SetScan(key, pattern, pageSize, cursor, pageOffset, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.SortedSets.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.SortedSets.Async.cs new file mode 100644 index 000000000..ae4c21186 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.SortedSets.Async.cs @@ -0,0 +1,124 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // SortedSet Async operations + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, CommandFlags flags) + => GetActiveDatabase().SortedSetAddAsync(key, member, score, flags); + + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetAddAsync(key, member, score, when, flags); + + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetAddAsync(key, member, score, when, flags); + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, CommandFlags flags) + => GetActiveDatabase().SortedSetAddAsync(key, values, flags); + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetAddAsync(key, values, when, flags); + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetAddAsync(key, values, when, flags); + + public Task SortedSetCombineAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetCombineAsync(operation, keys, weights, aggregate, flags); + + public Task SortedSetCombineWithScoresAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetCombineWithScoresAsync(operation, keys, weights, aggregate, flags); + + public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetCombineAndStoreAsync(operation, destination, first, second, aggregate, flags); + + public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetCombineAndStoreAsync(operation, destination, keys, weights, aggregate, flags); + + public Task SortedSetDecrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetDecrementAsync(key, member, value, flags); + + public Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetIncrementAsync(key, member, value, flags); + + public Task SortedSetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetIntersectionLengthAsync(keys, limit, flags); + + public Task SortedSetLengthAsync(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetLengthAsync(key, min, max, exclude, flags); + + public Task SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetLengthByValueAsync(key, min, max, exclude, flags); + + public Task SortedSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRandomMemberAsync(key, flags); + + public Task SortedSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRandomMembersAsync(key, count, flags); + + public Task SortedSetRandomMembersWithScoresAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRandomMembersWithScoresAsync(key, count, flags); + + public Task SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByRankAsync(key, start, stop, order, flags); + + public Task SortedSetRangeAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, RedisValue start, RedisValue stop, SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long? take = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeAndStoreAsync(sourceKey, destinationKey, start, stop, sortedSetOrder, exclude, order, skip, take, flags); + + public Task SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByRankWithScoresAsync(key, start, stop, order, flags); + + public Task SortedSetRangeByScoreAsync(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByScoreAsync(key, start, stop, exclude, order, skip, take, flags); + + public Task SortedSetRangeByScoreWithScoresAsync(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByScoreWithScoresAsync(key, start, stop, exclude, order, skip, take, flags); + + public Task SortedSetRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByValueAsync(key, min, max, exclude, skip, take, flags); + + public Task SortedSetRangeByValueAsync(RedisKey key, RedisValue min = default, RedisValue max = default, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByValueAsync(key, min, max, exclude, order, skip, take, flags); + + public Task SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRankAsync(key, member, order, flags); + + public Task SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemoveAsync(key, member, flags); + + public Task SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemoveAsync(key, members, flags); + + public Task SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemoveRangeByRankAsync(key, start, stop, flags); + + public Task SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemoveRangeByScoreAsync(key, start, stop, exclude, flags); + + public Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemoveRangeByValueAsync(key, min, max, exclude, flags); + + public System.Collections.Generic.IAsyncEnumerable SortedSetScanAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetScanAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + public Task SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetScoreAsync(key, member, flags); + + public Task SortedSetScoresAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetScoresAsync(key, members, flags); + + public Task SortedSetPopAsync(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetPopAsync(key, order, flags); + + public Task SortedSetPopAsync(RedisKey key, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetPopAsync(key, count, order, flags); + + public Task SortedSetPopAsync(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetPopAsync(keys, count, order, flags); + + public Task SortedSetUpdateAsync(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetUpdateAsync(key, member, score, when, flags); + + public Task SortedSetUpdateAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetUpdateAsync(key, values, when, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.SortedSets.cs b/src/StackExchange.Redis/MultiGroupDatabase.SortedSets.cs new file mode 100644 index 000000000..90814054f --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.SortedSets.cs @@ -0,0 +1,127 @@ +using System; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // SortedSet operations + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags) + => GetActiveDatabase().SortedSetAdd(key, member, score, flags); + + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetAdd(key, member, score, when, flags); + + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetAdd(key, member, score, when, flags); + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags flags) + => GetActiveDatabase().SortedSetAdd(key, values, flags); + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetAdd(key, values, when, flags); + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetAdd(key, values, when, flags); + + public RedisValue[] SortedSetCombine(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetCombine(operation, keys, weights, aggregate, flags); + + public SortedSetEntry[] SortedSetCombineWithScores(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetCombineWithScores(operation, keys, weights, aggregate, flags); + + public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetCombineAndStore(operation, destination, first, second, aggregate, flags); + + public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetCombineAndStore(operation, destination, keys, weights, aggregate, flags); + + public double SortedSetDecrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetDecrement(key, member, value, flags); + + public double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetIncrement(key, member, value, flags); + + public long SortedSetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetIntersectionLength(keys, limit, flags); + + public long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetLength(key, min, max, exclude, flags); + + public long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetLengthByValue(key, min, max, exclude, flags); + + public RedisValue SortedSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRandomMember(key, flags); + + public RedisValue[] SortedSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRandomMembers(key, count, flags); + + public SortedSetEntry[] SortedSetRandomMembersWithScores(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRandomMembersWithScores(key, count, flags); + + public RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByRank(key, start, stop, order, flags); + + public long SortedSetRangeAndStore(RedisKey sourceKey, RedisKey destinationKey, RedisValue start, RedisValue stop, SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long? take = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeAndStore(sourceKey, destinationKey, start, stop, sortedSetOrder, exclude, order, skip, take, flags); + + public SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByRankWithScores(key, start, stop, order, flags); + + public RedisValue[] SortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByScore(key, start, stop, exclude, order, skip, take, flags); + + public SortedSetEntry[] SortedSetRangeByScoreWithScores(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByScoreWithScores(key, start, stop, exclude, order, skip, take, flags); + + public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByValue(key, min, max, exclude, skip, take, flags); + + public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min = default, RedisValue max = default, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRangeByValue(key, min, max, exclude, order, skip, take, flags); + + public long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRank(key, member, order, flags); + + public bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemove(key, member, flags); + + public long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemove(key, members, flags); + + public long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemoveRangeByRank(key, start, stop, flags); + + public long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemoveRangeByScore(key, start, stop, exclude, flags); + + public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetRemoveRangeByValue(key, min, max, exclude, flags); + + public System.Collections.Generic.IEnumerable SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) + => GetActiveDatabase().SortedSetScan(key, pattern, pageSize, flags); + + public System.Collections.Generic.IEnumerable SortedSetScan(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetScan(key, pattern, pageSize, cursor, pageOffset, flags); + + public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetScore(key, member, flags); + + public double?[] SortedSetScores(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetScores(key, members, flags); + + public SortedSetEntry? SortedSetPop(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetPop(key, order, flags); + + public SortedSetEntry[] SortedSetPop(RedisKey key, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetPop(key, count, order, flags); + + public SortedSetPopResult SortedSetPop(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetPop(keys, count, order, flags); + + public bool SortedSetUpdate(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetUpdate(key, member, score, when, flags); + + public long SortedSetUpdate(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortedSetUpdate(key, values, when, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Streams.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.Streams.Async.cs new file mode 100644 index 000000000..89950e554 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Streams.Async.cs @@ -0,0 +1,131 @@ +using System; +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Stream Async operations + public Task StreamAutoClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAutoClaimIdsOnlyAsync(key, consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public Task StreamAutoClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAutoClaimAsync(key, consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAddAsync(key, streamField, streamValue, 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 trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAddAsync(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAddAsync(key, streamPairs, messageId, maxLength, useApproximateMaxLength, flags); + + public 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) + => GetActiveDatabase().StreamAddAsync(key, streamPairs, messageId, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public 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) + => GetActiveDatabase().StreamAddAsync(key, streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public 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) + => GetActiveDatabase().StreamAddAsync(key, streamPairs, idempotentId, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public Task StreamClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamClaimAsync(key, consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + + public Task StreamClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamClaimIdsOnlyAsync(key, consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + + public Task StreamConfigureAsync(RedisKey key, StreamConfiguration configuration, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamConfigureAsync(key, configuration, flags); + + public Task StreamConsumerGroupSetPositionAsync(RedisKey key, RedisValue groupName, RedisValue position, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamConsumerGroupSetPositionAsync(key, groupName, position, flags); + + public Task StreamConsumerInfoAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamConsumerInfoAsync(key, groupName, flags); + + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? position, CommandFlags flags) + => GetActiveDatabase().StreamCreateConsumerGroupAsync(key, groupName, position, flags); + + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamCreateConsumerGroupAsync(key, groupName, position, createStream, flags); + + public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamDeleteAsync(key, messageIds, flags); + + public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, StreamTrimMode mode, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamDeleteAsync(key, messageIds, mode, flags); + + public Task StreamDeleteConsumerAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamDeleteConsumerAsync(key, groupName, consumerName, flags); + + public Task StreamDeleteConsumerGroupAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamDeleteConsumerGroupAsync(key, groupName, flags); + + public Task StreamGroupInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamGroupInfoAsync(key, flags); + + public Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamInfoAsync(key, flags); + + public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamLengthAsync(key, flags); + + public Task StreamPendingAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamPendingAsync(key, groupName, flags); + + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamPendingMessagesAsync(key, groupName, count, consumerName, minId, maxId, flags); + + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, long? idle = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamPendingMessagesAsync(key, groupName, count, consumerName, minId, maxId, idle, flags); + + public Task StreamRangeAsync(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamRangeAsync(key, minId, maxId, count, messageOrder, flags); + + public Task StreamReadAsync(RedisKey key, RedisValue position, int? count = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadAsync(key, position, count, flags); + + public Task StreamReadAsync(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadAsync(streamPositions, countPerStream, flags); + + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position, int? count, CommandFlags flags) + => GetActiveDatabase().StreamReadGroupAsync(key, groupName, consumerName, position, count, flags); + + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadGroupAsync(key, groupName, consumerName, position, count, noAck, flags); + + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, TimeSpan? blockingTimeout = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadGroupAsync(key, groupName, consumerName, position, count, noAck, blockingTimeout, flags); + + public Task StreamReadGroupAsync(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, CommandFlags flags) + => GetActiveDatabase().StreamReadGroupAsync(streamPositions, groupName, consumerName, countPerStream, flags); + + public Task StreamReadGroupAsync(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadGroupAsync(streamPositions, groupName, consumerName, countPerStream, noAck, flags); + + public Task StreamReadGroupAsync(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, TimeSpan? blockingTimeout = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadGroupAsync(streamPositions, groupName, consumerName, countPerStream, noAck, blockingTimeout, flags); + + public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamTrimAsync(key, maxLength, useApproximateMaxLength, flags); + + public Task StreamTrimAsync(RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamTrimAsync(key, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public Task StreamTrimByMinIdAsync(RedisKey key, RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamTrimByMinIdAsync(key, minId, useApproximateMaxLength, limit, trimMode, flags); + + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAcknowledgeAsync(key, groupName, messageId, flags); + + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAcknowledgeAsync(key, groupName, messageIds, flags); + + public Task StreamAcknowledgeAndDeleteAsync(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue messageId, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAcknowledgeAndDeleteAsync(key, groupName, mode, messageId, flags); + + public Task StreamAcknowledgeAndDeleteAsync(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAcknowledgeAndDeleteAsync(key, groupName, mode, messageIds, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Streams.cs b/src/StackExchange.Redis/MultiGroupDatabase.Streams.cs new file mode 100644 index 000000000..e29a59cf9 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Streams.cs @@ -0,0 +1,130 @@ +using System; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // Stream operations + public StreamAutoClaimIdsOnlyResult StreamAutoClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAutoClaimIdsOnly(key, consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public StreamAutoClaimResult StreamAutoClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAutoClaim(key, consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAdd(key, streamField, streamValue, 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 trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAdd(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, int? maxLength = null, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAdd(key, streamPairs, messageId, maxLength, useApproximateMaxLength, flags); + + public 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) + => GetActiveDatabase().StreamAdd(key, streamPairs, messageId, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public 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) + => GetActiveDatabase().StreamAdd(key, streamField, streamValue, idempotentId, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public 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) + => GetActiveDatabase().StreamAdd(key, streamPairs, idempotentId, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public StreamEntry[] StreamClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamClaim(key, consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + + public RedisValue[] StreamClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamClaimIdsOnly(key, consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + + public void StreamConfigure(RedisKey key, StreamConfiguration configuration, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamConfigure(key, configuration, flags); + + public bool StreamConsumerGroupSetPosition(RedisKey key, RedisValue groupName, RedisValue position, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamConsumerGroupSetPosition(key, groupName, position, flags); + + public StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamConsumerInfo(key, groupName, flags); + + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? position, CommandFlags flags) + => GetActiveDatabase().StreamCreateConsumerGroup(key, groupName, position, flags); + + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamCreateConsumerGroup(key, groupName, position, createStream, flags); + + public long StreamDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamDelete(key, messageIds, flags); + + public long StreamDeleteConsumer(RedisKey key, RedisValue groupName, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamDeleteConsumer(key, groupName, consumerName, flags); + + public bool StreamDeleteConsumerGroup(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamDeleteConsumerGroup(key, groupName, flags); + + public StreamGroupInfo[] StreamGroupInfo(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamGroupInfo(key, flags); + + public StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamInfo(key, flags); + + public long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamLength(key, flags); + + public StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamPending(key, groupName, flags); + + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamPendingMessages(key, groupName, count, consumerName, minId, maxId, flags); + + public StreamEntry[] StreamRange(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamRange(key, minId, maxId, count, messageOrder, flags); + + public StreamEntry[] StreamRead(RedisKey key, RedisValue position, int? count = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamRead(key, position, count, flags); + + public RedisStream[] StreamRead(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamRead(streamPositions, countPerStream, flags); + + public StreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position, int? count, CommandFlags flags) + => GetActiveDatabase().StreamReadGroup(key, groupName, consumerName, position, count, flags); + + public StreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadGroup(key, groupName, consumerName, position, count, noAck, flags); + + public RedisStream[] StreamReadGroup(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, CommandFlags flags) + => GetActiveDatabase().StreamReadGroup(streamPositions, groupName, consumerName, countPerStream, flags); + + public RedisStream[] StreamReadGroup(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadGroup(streamPositions, groupName, consumerName, countPerStream, noAck, flags); + + public long StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength = false, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamTrim(key, maxLength, useApproximateMaxLength, flags); + + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAcknowledge(key, groupName, messageId, flags); + + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAcknowledge(key, groupName, messageIds, flags); + + public StreamTrimResult StreamAcknowledgeAndDelete(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue messageId, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAcknowledgeAndDelete(key, groupName, mode, messageId, flags); + + public StreamTrimResult[] StreamAcknowledgeAndDelete(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamAcknowledgeAndDelete(key, groupName, mode, messageIds, flags); + + public StreamTrimResult[] StreamDelete(RedisKey key, RedisValue[] messageIds, StreamTrimMode mode, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamDelete(key, messageIds, mode, flags); + + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, long? idle = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamPendingMessages(key, groupName, count, consumerName, minId, maxId, idle, flags); + + public StreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, TimeSpan? blockingTimeout = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadGroup(key, groupName, consumerName, position, count, noAck, blockingTimeout, flags); + + public RedisStream[] StreamReadGroup(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, TimeSpan? blockingTimeout = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamReadGroup(streamPositions, groupName, consumerName, countPerStream, noAck, blockingTimeout, flags); + + public long StreamTrim(RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamTrim(key, maxLength, useApproximateMaxLength, limit, trimMode, flags); + + public long StreamTrimByMinId(RedisKey key, RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StreamTrimByMinId(key, minId, useApproximateMaxLength, limit, trimMode, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Strings.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.Strings.Async.cs new file mode 100644 index 000000000..9dba49a82 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Strings.Async.cs @@ -0,0 +1,119 @@ +using System; +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // String Async operations + public Task StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringAppendAsync(key, value, flags); + + public Task StringBitCountAsync(RedisKey key, long start, long end, CommandFlags flags) + => GetActiveDatabase().StringBitCountAsync(key, start, end, flags); + + public Task StringBitCountAsync(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringBitCountAsync(key, start, end, indexType, flags); + + public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringBitOperationAsync(operation, destination, first, second, flags); + + public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringBitOperationAsync(operation, destination, keys, flags); + + public Task StringBitPositionAsync(RedisKey key, bool bit, long start, long end, CommandFlags flags) + => GetActiveDatabase().StringBitPositionAsync(key, bit, start, end, flags); + + public Task StringBitPositionAsync(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringBitPositionAsync(key, bit, start, end, indexType, flags); + + public Task StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringDecrementAsync(key, value, flags); + + public Task StringDecrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringDecrementAsync(key, value, flags); + + public Task StringDeleteAsync(RedisKey key, ValueCondition when, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringDeleteAsync(key, when, flags); + + public Task StringDigestAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringDigestAsync(key, flags); + + public Task StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetAsync(key, flags); + + public Task StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetAsync(keys, flags); + + public Task?> StringGetLeaseAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetLeaseAsync(key, flags); + + public Task StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetBitAsync(key, offset, flags); + + public Task StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetRangeAsync(key, start, end, flags); + + public Task StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetSetAsync(key, value, flags); + + public Task StringGetSetExpiryAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetSetExpiryAsync(key, expiry, flags); + + public Task StringGetSetExpiryAsync(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetSetExpiryAsync(key, expiry, flags); + + public Task StringGetDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetDeleteAsync(key, flags); + + public Task StringGetWithExpiryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetWithExpiryAsync(key, flags); + + public Task StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringIncrementAsync(key, value, flags); + + public Task StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringIncrementAsync(key, value, flags); + + public Task StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringLengthAsync(key, flags); + + public Task StringLongestCommonSubsequenceAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringLongestCommonSubsequenceAsync(first, second, flags); + + public Task StringLongestCommonSubsequenceLengthAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringLongestCommonSubsequenceLengthAsync(first, second, flags); + + public Task StringLongestCommonSubsequenceWithMatchesAsync(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringLongestCommonSubsequenceWithMatchesAsync(first, second, minLength, flags); + + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when) + => GetActiveDatabase().StringSetAsync(key, value, expiry, when); + + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) + => GetActiveDatabase().StringSetAsync(key, value, expiry, when, flags); + + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, bool keepTtl, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSetAsync(key, value, expiry, keepTtl, when, flags); + + public Task StringSetAsync(RedisKey key, RedisValue value, Expiration expiry = default, ValueCondition when = default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSetAsync(key, value, expiry, when, flags); + + public Task StringSetAsync(System.Collections.Generic.KeyValuePair[] values, When when, CommandFlags flags) + => GetActiveDatabase().StringSetAsync(values, when, flags); + + public Task StringSetAsync(System.Collections.Generic.KeyValuePair[] values, When when = When.Always, Expiration expiry = default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSetAsync(values, when, expiry, flags); + + public Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) + => GetActiveDatabase().StringSetAndGetAsync(key, value, expiry, when, flags); + + public Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSetAndGetAsync(key, value, expiry, keepTtl, when, flags); + + public Task StringSetBitAsync(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSetBitAsync(key, offset, bit, flags); + + public Task StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSetRangeAsync(key, offset, value, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.Strings.cs b/src/StackExchange.Redis/MultiGroupDatabase.Strings.cs new file mode 100644 index 000000000..ffab62787 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.Strings.cs @@ -0,0 +1,118 @@ +using System; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // String operations + public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringAppend(key, value, flags); + + public long StringBitCount(RedisKey key, long start, long end, CommandFlags flags) + => GetActiveDatabase().StringBitCount(key, start, end, flags); + + public long StringBitCount(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringBitCount(key, start, end, indexType, flags); + + public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringBitOperation(operation, destination, first, second, flags); + + public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringBitOperation(operation, destination, keys, flags); + + public long StringBitPosition(RedisKey key, bool bit, long start, long end, CommandFlags flags) + => GetActiveDatabase().StringBitPosition(key, bit, start, end, flags); + + public long StringBitPosition(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringBitPosition(key, bit, start, end, indexType, flags); + + public long StringDecrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringDecrement(key, value, flags); + + public double StringDecrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringDecrement(key, value, flags); + + public bool StringDelete(RedisKey key, ValueCondition when, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringDelete(key, when, flags); + + public ValueCondition? StringDigest(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringDigest(key, flags); + + public RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGet(key, flags); + + public RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGet(keys, flags); + + public Lease? StringGetLease(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetLease(key, flags); + + public bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetBit(key, offset, flags); + + public RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetRange(key, start, end, flags); + + public RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetSet(key, value, flags); + + public RedisValue StringGetSetExpiry(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetSetExpiry(key, expiry, flags); + + public RedisValue StringGetSetExpiry(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetSetExpiry(key, expiry, flags); + + public RedisValue StringGetDelete(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetDelete(key, flags); + + public RedisValueWithExpiry StringGetWithExpiry(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringGetWithExpiry(key, flags); + + public long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringIncrement(key, value, flags); + + public double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringIncrement(key, value, flags); + + public long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringLength(key, flags); + + public string? StringLongestCommonSubsequence(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringLongestCommonSubsequence(first, second, flags); + + public long StringLongestCommonSubsequenceLength(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringLongestCommonSubsequenceLength(first, second, flags); + + public LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringLongestCommonSubsequenceWithMatches(first, second, minLength, flags); + + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when) + => GetActiveDatabase().StringSet(key, value, expiry, when); + + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) + => GetActiveDatabase().StringSet(key, value, expiry, when, flags); + + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, bool keepTtl, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSet(key, value, expiry, keepTtl, when, flags); + + public bool StringSet(RedisKey key, RedisValue value, Expiration expiry = default, ValueCondition when = default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSet(key, value, expiry, when, flags); + + public bool StringSet(System.Collections.Generic.KeyValuePair[] values, When when, CommandFlags flags) + => GetActiveDatabase().StringSet(values, when, flags); + + public bool StringSet(System.Collections.Generic.KeyValuePair[] values, When when = When.Always, Expiration expiry = default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSet(values, when, expiry, flags); + + public RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) + => GetActiveDatabase().StringSetAndGet(key, value, expiry, when, flags); + + public RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSetAndGet(key, value, expiry, keepTtl, when, flags); + + public bool StringSetBit(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSetBit(key, offset, bit, flags); + + public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().StringSetRange(key, offset, value, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.VectorSets.Async.cs b/src/StackExchange.Redis/MultiGroupDatabase.VectorSets.Async.cs new file mode 100644 index 000000000..f4c11eb24 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.VectorSets.Async.cs @@ -0,0 +1,55 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // VectorSet Async operations + public Task VectorSetAddAsync(RedisKey key, VectorSetAddRequest request, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetAddAsync(key, request, flags); + + public Task VectorSetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetLengthAsync(key, flags); + + public Task VectorSetDimensionAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetDimensionAsync(key, flags); + + public Task?> VectorSetGetApproximateVectorAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetGetApproximateVectorAsync(key, member, flags); + + public Task VectorSetGetAttributesJsonAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetGetAttributesJsonAsync(key, member, flags); + + public Task VectorSetInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetInfoAsync(key, flags); + + public Task VectorSetContainsAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetContainsAsync(key, member, flags); + + public Task?> VectorSetGetLinksAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetGetLinksAsync(key, member, flags); + + public Task?> VectorSetGetLinksWithScoresAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetGetLinksWithScoresAsync(key, member, flags); + + public Task VectorSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRandomMemberAsync(key, flags); + + public Task VectorSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRandomMembersAsync(key, count, flags); + + public Task VectorSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRemoveAsync(key, member, flags); + + public Task VectorSetSetAttributesJsonAsync(RedisKey key, RedisValue member, string attributesJson, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetSetAttributesJsonAsync(key, member, attributesJson, flags); + + public Task?> VectorSetSimilaritySearchAsync(RedisKey key, VectorSetSimilaritySearchRequest query, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetSimilaritySearchAsync(key, query, flags); + + public Task?> VectorSetRangeAsync(RedisKey key, RedisValue start = default, RedisValue end = default, long count = -1, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRangeAsync(key, start, end, count, exclude, flags); + + public System.Collections.Generic.IAsyncEnumerable VectorSetRangeEnumerateAsync(RedisKey key, RedisValue start = default, RedisValue end = default, long count = 100, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRangeEnumerateAsync(key, start, end, count, exclude, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.VectorSets.cs b/src/StackExchange.Redis/MultiGroupDatabase.VectorSets.cs new file mode 100644 index 000000000..64470da4e --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.VectorSets.cs @@ -0,0 +1,53 @@ +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase +{ + // VectorSet operations + public bool VectorSetAdd(RedisKey key, VectorSetAddRequest request, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetAdd(key, request, flags); + + public long VectorSetLength(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetLength(key, flags); + + public int VectorSetDimension(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetDimension(key, flags); + + public Lease? VectorSetGetApproximateVector(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetGetApproximateVector(key, member, flags); + + public string? VectorSetGetAttributesJson(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetGetAttributesJson(key, member, flags); + + public VectorSetInfo? VectorSetInfo(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetInfo(key, flags); + + public bool VectorSetContains(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetContains(key, member, flags); + + public Lease? VectorSetGetLinks(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetGetLinks(key, member, flags); + + public Lease? VectorSetGetLinksWithScores(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetGetLinksWithScores(key, member, flags); + + public RedisValue VectorSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRandomMember(key, flags); + + public RedisValue[] VectorSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRandomMembers(key, count, flags); + + public bool VectorSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRemove(key, member, flags); + + public bool VectorSetSetAttributesJson(RedisKey key, RedisValue member, string attributesJson, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetSetAttributesJson(key, member, attributesJson, flags); + + public Lease? VectorSetSimilaritySearch(RedisKey key, VectorSetSimilaritySearchRequest query, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetSimilaritySearch(key, query, flags); + + public Lease VectorSetRange(RedisKey key, RedisValue start = default, RedisValue end = default, long count = -1, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRange(key, start, end, count, exclude, flags); + + public System.Collections.Generic.IEnumerable VectorSetRangeEnumerate(RedisKey key, RedisValue start = default, RedisValue end = default, long count = 100, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().VectorSetRangeEnumerate(key, start, end, count, exclude, flags); +} diff --git a/src/StackExchange.Redis/MultiGroupDatabase.cs b/src/StackExchange.Redis/MultiGroupDatabase.cs new file mode 100644 index 000000000..b4248c2dc --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupDatabase.cs @@ -0,0 +1,95 @@ +using System; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupDatabase(MultiGroupMultiplexer parent, int database, object? asyncState) + : IDatabase +{ + public object? AsyncState => asyncState; + public int Database => database < 0 ? GetActiveDatabase().Database : database; + + public IConnectionMultiplexer Multiplexer => parent; + + // for high DB numbers this might allocate even for null async-state scenarios; unavoidable for now + private IDatabase GetActiveDatabase() => parent.Active.GetDatabase(database, asyncState); + + // Core methods + public IBatch CreateBatch(object? asyncState = null) + => GetActiveDatabase().CreateBatch(asyncState); + + public ITransaction CreateTransaction(object? asyncState = null) + => GetActiveDatabase().CreateTransaction(asyncState); + + public void KeyMigrate(RedisKey key, System.Net.EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().KeyMigrate(key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); + + public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().DebugObject(key, flags); + + public System.Net.EndPoint? IdentifyEndpoint(RedisKey key = default, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().IdentifyEndpoint(key, flags); + + public bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().IsConnected(key, flags); + + public System.TimeSpan Ping(CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().Ping(flags); + + public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().Publish(channel, message, flags); + + public RedisResult Execute(string command, params object[] args) + => GetActiveDatabase().Execute(command, args); + + public RedisResult Execute(string command, System.Collections.Generic.ICollection args, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().Execute(command, args, flags); + + public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluate(script, keys, values, flags); + + public RedisResult ScriptEvaluate(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluate(hash, keys, values, flags); + + public RedisResult ScriptEvaluate(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluate(script, parameters, flags); + + public RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluate(script, parameters, flags); + + public RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluateReadOnly(script, keys, values, flags); + + public RedisResult ScriptEvaluateReadOnly(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().ScriptEvaluateReadOnly(hash, keys, values, flags); + + public bool LockExtend(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().LockExtend(key, value, expiry, flags); + + public RedisValue LockQuery(RedisKey key, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().LockQuery(key, flags); + + public bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().LockRelease(key, value, flags); + + public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().LockTake(key, value, expiry, flags); + + public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().Sort(key, skip, take, order, sortType, by, get, flags); + + public long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) + => GetActiveDatabase().SortAndStore(destination, key, skip, take, order, sortType, by, get, flags); + + // IRedisAsync methods + public bool TryWait(System.Threading.Tasks.Task task) + => GetActiveDatabase().TryWait(task); + + public void Wait(System.Threading.Tasks.Task task) + => GetActiveDatabase().Wait(task); + + public T Wait(System.Threading.Tasks.Task task) + => GetActiveDatabase().Wait(task); + + public void WaitAll(params System.Threading.Tasks.Task[] tasks) + => GetActiveDatabase().WaitAll(tasks); +} diff --git a/src/StackExchange.Redis/MultiGroupMultiplexer.cs b/src/StackExchange.Redis/MultiGroupMultiplexer.cs new file mode 100644 index 000000000..8b9cb7f10 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupMultiplexer.cs @@ -0,0 +1,954 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using StackExchange.Redis.Maintenance; +using StackExchange.Redis.Profiling; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + /// + /// Creates a new instance that manages connections to multiple + /// redundant configurations, based on their availability and relative . + /// + /// The initial configurations to connect to. + /// The to log to. +#pragma warning disable RS0016, RS0026 + public static Task ConnectGroupAsync(ConnectionGroupMember[] members, TextWriter? log = null) +#pragma warning restore RS0016, RS0026 + { + // create a defensive copy of the array; we don't want callers being able to radically swap things! + members = (ConnectionGroupMember[])members.Clone(); + return MultiGroupMultiplexer.ConnectAsync(members, log); + } + + /// + /// Creates a new instance that manages connections to multiple + /// redundant configurations, based on their availability and relative . + /// + /// An initial configuration to connect to. + /// An additional initial configuration to connect to. + /// The to log to. +#pragma warning disable RS0016, RS0026 + public static Task ConnectGroupAsync( + ConnectionGroupMember member0, + ConnectionGroupMember member1, + TextWriter? log = null) +#pragma warning restore RS0016, RS0026 + { + return MultiGroupMultiplexer.ConnectAsync([member0, member1], log); + } +} + +/// +/// A configured member of a . +/// +#pragma warning disable RS0016, RS0026 +public sealed partial class ConnectionGroupMember(ConfigurationOptions configuration, string name = "") +#pragma warning restore RS0016, RS0026 +{ + /// + /// Create a new from a configuration string. + /// +#pragma warning disable RS0016, RS0026 + public ConnectionGroupMember(string configuration, string name = "") : this( + ConfigurationOptions.Parse(configuration)) +#pragma warning restore RS0016, RS0026 + { + } + + internal ConfigurationOptions Configuration => configuration; + + private int _activated; // each member can only be activated once + + /// + public override string ToString() => Name; + + private ConnectionMultiplexer? _muxer; + + internal ConnectionMultiplexer Multiplexer => _muxer ?? ThrowNoMuxer(); + + [DoesNotReturn] + private static ConnectionMultiplexer ThrowNoMuxer() => + throw new InvalidOperationException("Member is not connected."); + + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract + internal void SetMultiplexer(ConnectionMultiplexer muxer) + => Interlocked.Exchange(ref _muxer, muxer ?? ThrowNoMuxer()); + + internal ConnectionMultiplexer? ClearMultiplexer() => Interlocked.Exchange(ref _muxer, null); + + internal void Init(int index) + { + // add a name if not provided + if (string.IsNullOrWhiteSpace(Name)) + { + var ep = Configuration.EndPoints.FirstOrDefault(); + if (ep is null) + { + Name = index.ToString(); + } + else + { + Name = Format.ToString(ep); + } + } + + // check not already attached + if (Interlocked.CompareExchange(ref _activated, 1, 0) != 0) + { + throw new InvalidOperationException( + $"Member '{Name}' is already associated with a group, and cannot be reused."); + } + } + + /// + /// Indicates whether this group is currently connected. + /// + public bool IsConnected { get; private set; } + + /// + /// The name of this group member. + /// + public string Name { get; private set; } = name; + + /// + /// The relative weight of this group member; higher is preferred. + /// + public double Weight + { + // avoid "tearing", since we can't rule out this being updated concurrently, and the runtime + // only guarantees atomicity for 32-bit reads/writes + get => BitConverter.Int64BitsToDouble(Interlocked.Read(ref _weight64)); + set => Interlocked.Exchange(ref _weight64, BitConverter.DoubleToInt64Bits(value)); + } + + private long _weight64 = BitConverter.DoubleToInt64Bits(1.0); + + /// + /// The measured latency to this member. + /// + public TimeSpan Latency => _latencyTicks is uint.MaxValue ? TimeSpan.MaxValue : TimeSpan.FromTicks(_latencyTicks); + + private uint _latencyTicks = uint.MaxValue; + + internal void SetLatency(uint ticks) => _latencyTicks = ticks; + + internal static uint ToLatencyTicks(TimeSpan latency) + { + long ticks = latency.Ticks; + if (ticks <= 0) + { + return 0; + } + return ticks > uint.MaxValue ? uint.MaxValue : (uint)ticks; + } + + internal void SetLatency(TimeSpan latency) => SetLatency(ToLatencyTicks(latency)); + + internal static ConnectionGroupMember? Select(ConnectionGroupMember? x, ConnectionGroupMember? y) + { + if (x is null) return y; + if (y is null) return x; + + // always prefer a connected endpoint + bool xc = x.IsConnected, yc = y.IsConnected; + if (xc != yc) return xc ? x : y; + + // prefer higher weight + double xw = x.Weight, yw = y.Weight; + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (xw != yw) return xw > yw ? x : y; + + // then by latency + uint xl = x._latencyTicks, yl = y._latencyTicks; + return xl <= yl ? x : y; + } + + internal GroupConnectionChangedEventArgs.ChangeType UpdateState() + { + bool isConnected; + if (_muxer is { IsConnected: true } muxer) + { + isConnected = true; + SetLatency(muxer.UpdateLatency()); + } + else + { + isConnected = false; + } + + var oldConnected = IsConnected; + IsConnected = isConnected; + + return isConnected == oldConnected ? GroupConnectionChangedEventArgs.ChangeType.Unknown + : isConnected ? GroupConnectionChangedEventArgs.ChangeType.Reconnected + : GroupConnectionChangedEventArgs.ChangeType.Disconnected; + } +} + +internal sealed partial class MultiGroupMultiplexer : IConnectionGroup +{ + private ConnectionMultiplexer? _active; + private ConnectionGroupMember[] _members; + + public override string ToString() + { + var muxer = _active; + ConnectionGroupMember? member = null; + if (muxer is not null) + { + foreach (var m in _members) + { + if (ReferenceEquals(muxer, m.Multiplexer)) + { + member = m; + break; + } + } + } + + return member is null ? "No active connection" : $"Connected to {member.Name}"; + } + + public ReadOnlySpan GetMembers() => _members; + + internal ConnectionMultiplexer Active + { + get + { + return _active ?? Throw(); + + [DoesNotReturn] + static ConnectionMultiplexer Throw() => + throw new InvalidOperationException("All connections are unavailable."); + } + } + + internal ConnectionGroupMember ActiveMember + { + get + { + var active = _active; + foreach (var member in _members) + { + if (ReferenceEquals(active, member.Multiplexer)) + { + return member; + } + } + + return Throw(); + + [DoesNotReturn] + static ConnectionGroupMember Throw() => + throw new InvalidOperationException("All connections are unavailable."); + } + } + + internal static async Task ConnectAsync(ConnectionGroupMember[] members, TextWriter? log) + { + for (int i = 0; i < members.Length; i++) + { + members[i].Init(i); + } + + var pending = new Task[members.Length]; + for (int i = 0; i < members.Length; i++) + { + var config = members[i].Configuration; + config.AbortOnConnectFail = false; + config.HeartbeatConsistencyChecks = true; + pending[i] = ConnectionMultiplexer.ConnectAsync(config, log); + } + + for (int i = 0; i < pending.Length; i++) + { + var muxer = await pending[i].ConfigureAwait(false); + members[i].SetMultiplexer(muxer); + } + + return new MultiGroupMultiplexer(members); + } + + private MultiGroupMultiplexer(ConnectionGroupMember[] members) + { + _members = members; + _active = null; + SelectPreferredGroup(); + } + + internal void SelectPreferredGroup() + { + var previousMuxer = _active; + ConnectionGroupMember? preferredMember = null, previousMember = null; + var members = _members; + foreach (var member in _members) + { + var delta = member.UpdateState(); + if (delta != GroupConnectionChangedEventArgs.ChangeType.Unknown) + { + OnConnectionChanged(delta, member); + } + } + + foreach (var member in members) + { + if (previousMember is null && ReferenceEquals(member.Multiplexer, previousMuxer)) + { + previousMember = member; + } + + if (member.IsConnected) + { + preferredMember = ConnectionGroupMember.Select(preferredMember, member); + } + } + + _active = preferredMember?.Multiplexer; + + if (preferredMember is not null && !ReferenceEquals(preferredMember, previousMember)) + { + OnConnectionChanged( + GroupConnectionChangedEventArgs.ChangeType.ActiveChanged, + preferredMember, + previousMember); + } + } + + private List DropAll() + { + _active = null; + var members = Interlocked.Exchange(ref _members, []); + if (members.Length is 0) return []; + var muxers = new List(members.Length); + foreach (var member in members) + { + var muxer = member.ClearMultiplexer(); + if (muxer is not null) muxers.Add(muxer); + } + + return muxers; + } + + public void Dispose() + { + foreach (var muxer in DropAll()) + { + muxer.Dispose(); + } + } + + public async ValueTask DisposeAsync() + { + foreach (var muxer in DropAll()) + { + await muxer.DisposeAsync(); + } + } + + public string ClientName => Active.ClientName; + public string Configuration => Active.Configuration; + public int TimeoutMilliseconds => Active.TimeoutMilliseconds; + + public long OperationCount + { + get + { + long count = 0; + foreach (var member in _members) + { + count += member.Multiplexer.OperationCount; + } + + return count; + } + } + + [Obsolete] + public bool PreserveAsyncOrder + { + get => Active.PreserveAsyncOrder; + set => Active.PreserveAsyncOrder = value; + } + + public bool IsConnected => Active.IsConnected; + public bool IsConnecting => Active.IsConnecting; + + [Obsolete] + public bool IncludeDetailInExceptions + { + get => Active.IncludeDetailInExceptions; + set => Active.IncludeDetailInExceptions = value; + } + + public int StormLogThreshold + { + get => Active.StormLogThreshold; + set => Active.StormLogThreshold = value; + } + + private Func? _profilingSessionProvider; + + public void RegisterProfiler(Func profilingSessionProvider) + { + _profilingSessionProvider = profilingSessionProvider; + foreach (var member in _members) + { + member.Multiplexer.RegisterProfiler(profilingSessionProvider); + } + } + + public ServerCounters GetCounters() => Active.GetCounters(); + + private EventHandler? _errorMessage; + + public event EventHandler? ErrorMessage + { + add + { + if (AddHandler(ref _errorMessage, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ErrorMessage += value; + } + } + } + remove + { + if (RemoveHandler(ref _errorMessage, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ErrorMessage -= value; + } + } + } + } + + /// + /// Add a handler, and return true if this is the *first* handler, which means we should subscribe the dependents. + /// + private static bool AddHandler(ref T? field, T? value) where T : Delegate + { + if (value is null) return false; + while (true) // loop until we win (competition) + { + var oldValue = field; + var newValue = oldValue is null ? value : (T)Delegate.Combine(oldValue, value); + + if (ReferenceEquals(Interlocked.CompareExchange(ref field, newValue, oldValue), oldValue)) + { + return oldValue is null; + } + } + } + + /// + /// Remove a handler, and return true if this is the *last* handler, which means we should unsubscribe the dependents. + /// + private static bool RemoveHandler(ref T? field, T? value) where T : Delegate + { + if (value is null) return false; + while (true) // loop until we win (competition) + { + var oldValue = field; + var newValue = oldValue is null ? null : (T?)Delegate.Remove(oldValue, value); + + if (ReferenceEquals(Interlocked.CompareExchange(ref field, newValue, oldValue), oldValue)) + { + return newValue is null; + } + } + } + + /// + /// Subscribe a child multiplexer to all local event handlers that have subscribers. + /// + private void AddEventHandlers(ConnectionMultiplexer muxer) + { + muxer.ErrorMessage += _errorMessage; + muxer.ConnectionFailed += _connectionFailed; + muxer.InternalError += _internalError; + muxer.ConnectionRestored += _connectionRestored; + muxer.ConfigurationChanged += _configurationChanged; + muxer.ConfigurationChangedBroadcast += _configurationChangedBroadcast; + muxer.ServerMaintenanceEvent += _serverMaintenanceEvent; + muxer.HashSlotMoved += _hashSlotMoved; + } + + /// + /// Unsubscribe a child multiplexer from all local event handlers. + /// + private void RemoveEventHandlers(ConnectionMultiplexer? muxer) + { + if (muxer is null) return; + muxer.ErrorMessage -= _errorMessage; + muxer.ConnectionFailed -= _connectionFailed; + muxer.InternalError -= _internalError; + muxer.ConnectionRestored -= _connectionRestored; + muxer.ConfigurationChanged -= _configurationChanged; + muxer.ConfigurationChangedBroadcast -= _configurationChangedBroadcast; + muxer.ServerMaintenanceEvent -= _serverMaintenanceEvent; + muxer.HashSlotMoved -= _hashSlotMoved; + } + + private EventHandler? _connectionFailed; + + public event EventHandler? ConnectionFailed + { + add + { + if (AddHandler(ref _connectionFailed, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ConnectionFailed += value; + } + } + } + remove + { + if (RemoveHandler(ref _connectionFailed, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ConnectionFailed -= value; + } + } + } + } + + private EventHandler? _internalError; + + public event EventHandler? InternalError + { + add + { + if (AddHandler(ref _internalError, value)) + { + foreach (var member in _members) + { + member.Multiplexer.InternalError += value; + } + } + } + remove + { + if (RemoveHandler(ref _internalError, value)) + { + foreach (var member in _members) + { + member.Multiplexer.InternalError -= value; + } + } + } + } + + private EventHandler? _connectionRestored; + + public event EventHandler? ConnectionRestored + { + add + { + if (AddHandler(ref _connectionRestored, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ConnectionRestored += value; + } + } + } + remove + { + if (RemoveHandler(ref _connectionRestored, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ConnectionRestored -= value; + } + } + } + } + + private EventHandler? _configurationChanged; + + public event EventHandler? ConfigurationChanged + { + add + { + if (AddHandler(ref _configurationChanged, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ConfigurationChanged += value; + } + } + } + remove + { + if (RemoveHandler(ref _configurationChanged, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ConfigurationChanged -= value; + } + } + } + } + + private EventHandler? _configurationChangedBroadcast; + + public event EventHandler? ConfigurationChangedBroadcast + { + add + { + if (AddHandler(ref _configurationChangedBroadcast, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ConfigurationChangedBroadcast += value; + } + } + } + remove + { + if (RemoveHandler(ref _configurationChangedBroadcast, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ConfigurationChangedBroadcast -= value; + } + } + } + } + + private EventHandler? _serverMaintenanceEvent; + + public event EventHandler? ServerMaintenanceEvent + { + add + { + if (AddHandler(ref _serverMaintenanceEvent, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ServerMaintenanceEvent += value; + } + } + } + remove + { + if (RemoveHandler(ref _serverMaintenanceEvent, value)) + { + foreach (var member in _members) + { + member.Multiplexer.ServerMaintenanceEvent -= value; + } + } + } + } + + public EndPoint[] GetEndPoints(bool configuredOnly = false) => Active.GetEndPoints(configuredOnly); + + public void Wait(Task task) => Active.Wait(task); + + public T Wait(Task task) => Active.Wait(task); + + public void WaitAll(params Task[] tasks) => Active.WaitAll(tasks); + + private EventHandler? _hashSlotMoved; + + public event EventHandler? HashSlotMoved + { + add + { + if (AddHandler(ref _hashSlotMoved, value)) + { + foreach (var member in _members) + { + member.Multiplexer.HashSlotMoved += value; + } + } + } + remove + { + if (RemoveHandler(ref _hashSlotMoved, value)) + { + foreach (var member in _members) + { + member.Multiplexer.HashSlotMoved -= value; + } + } + } + } + + public int HashSlot(RedisKey key) => Active.HashSlot(key); + + private ISubscriber? _defaultSubscriber; + + public ISubscriber GetSubscriber(object? asyncState = null) + { + if (asyncState is null) return _defaultSubscriber ??= new MultiGroupSubscriber(this, null); + return new MultiGroupSubscriber(this, asyncState); + } + + public IDatabase GetDatabase(int db = -1, object? asyncState = null) + { + if (asyncState is null & db >= -1 & db <= ConnectionMultiplexer.MaxCachedDatabaseInstance) + { + return _databases[db + 1] ??= new MultiGroupDatabase(this, db, null); + } + + return new MultiGroupDatabase(this, db, asyncState); + } + + private readonly IDatabase?[] _databases = new IDatabase?[ConnectionMultiplexer.MaxCachedDatabaseInstance + 2]; + + public IServer GetServer(string host, int port, object? asyncState = null) + { + Exception ex; + try + { + // try "active" first, and preserve the exception + return Active.GetServer(host, port, asyncState); + } + catch (Exception e) + { + ex = e; + } + + foreach (var member in _members) + { + try + { + return member.Multiplexer.GetServer(host, port, asyncState); + } + catch (Exception e) { Debug.WriteLine(e.Message); } + } + + throw ex; + } + + public IServer GetServer(string hostAndPort, object? asyncState = null) + { + Exception ex; + try + { + // try "active" first, and preserve the exception + return Active.GetServer(hostAndPort, asyncState); + } + catch (Exception e) + { + ex = e; + } + + foreach (var member in _members) + { + try + { + return member.Multiplexer.GetServer(hostAndPort, asyncState); + } + catch (Exception e) { Debug.WriteLine(e.Message); } + } + + throw ex; + } + + public IServer GetServer(IPAddress host, int port) + { + Exception ex; + try + { + // try "active" first, and preserve the exception + return Active.GetServer(host, port); + } + catch (Exception e) + { + ex = e; + } + + foreach (var member in _members) + { + try + { + return member.Multiplexer.GetServer(host, port); + } + catch (Exception e) { Debug.WriteLine(e.Message); } + } + + throw ex; + } + + public IServer GetServer(EndPoint endpoint, object? asyncState = null) + { + Exception ex; + try + { + // try "active" first, and preserve the exception + return Active.GetServer(endpoint, asyncState); + } + catch (Exception e) + { + ex = e; + } + + foreach (var member in _members) + { + try + { + return member.Multiplexer.GetServer(endpoint, asyncState); + } + catch (Exception e) { Debug.WriteLine(e.Message); } + } + + throw ex; + } + + public IServer GetServer(RedisKey key, object? asyncState = null, CommandFlags flags = CommandFlags.None) => + Active.GetServer(key, asyncState, flags); + + public IServer[] GetServers() => Active.GetServers(); + + public Task ConfigureAsync(TextWriter? log = null) => Active.ConfigureAsync(log); + + public bool Configure(TextWriter? log = null) => Active.Configure(log); + + public string GetStatus() => Active.GetStatus(); + + public void GetStatus(TextWriter log) => Active.GetStatus(log); + + public void Close(bool allowCommandsToComplete = true) => Active.Close(allowCommandsToComplete); + + public Task CloseAsync(bool allowCommandsToComplete = true) => Active.CloseAsync(allowCommandsToComplete); + + public string? GetStormLog() => Active.GetStormLog(); + + public void ResetStormLog() => Active.ResetStormLog(); + + public long PublishReconfigure(CommandFlags flags = CommandFlags.None) => Active.PublishReconfigure(flags); + + public Task PublishReconfigureAsync(CommandFlags flags = CommandFlags.None) => + Active.PublishReconfigureAsync(flags); + + public int GetHashSlot(RedisKey key) => Active.GetHashSlot(key); + + public void ExportConfiguration(Stream destination, ExportOptions options = ExportOptions.All) => + Active.ExportConfiguration(destination, options); + + private readonly HashSet _suffixes = new(); // in case we need to add to a new muxer + public void AddLibraryNameSuffix(string suffix) + { + if (string.IsNullOrWhiteSpace(suffix)) return; // trivial + bool isNew; + lock (_suffixes) + { + isNew = _suffixes.Add(suffix); + } + + if (isNew) + { + foreach (var member in _members) + { + member.Multiplexer.AddLibraryNameSuffix(suffix); + } + } + } + + public event EventHandler? ConnectionChanged; + + private void OnConnectionChanged( + GroupConnectionChangedEventArgs.ChangeType changeType, + ConnectionGroupMember member, + ConnectionGroupMember? previousMember = null) + { + var handler = ConnectionChanged; + if (handler is not null) + { + new GroupConnectionChangedEventArgs(changeType, member, previousMember) + .CompleteAsWorker(handler, this); + } + } + + public async Task AddAsync(ConnectionGroupMember member, TextWriter? log = null) + { + // connect + member.Init(_members.Length); + member.Configuration.HeartbeatConsistencyChecks = true; + var muxer = await ConnectionMultiplexer.ConnectAsync(member.Configuration, log).ConfigureAwait(false); + member.SetMultiplexer(muxer); + + // apply any shared hooks + AddEventHandlers(muxer); + if (_profilingSessionProvider is not null) muxer.RegisterProfiler(_profilingSessionProvider); + lock (_suffixes) + { + foreach (var suffix in _suffixes) + { + muxer.AddLibraryNameSuffix(suffix); + } + } + + // update the members array + while (true) + { + var arr = _members; + var newArr = new ConnectionGroupMember[arr.Length + 1]; + Array.Copy(arr, 0, newArr, 0, arr.Length); + newArr[arr.Length] = member; + if (Interlocked.CompareExchange(ref _members, newArr, arr) == arr) break; + } + + OnConnectionChanged(GroupConnectionChangedEventArgs.ChangeType.Added, member); + SelectPreferredGroup(); + + // pub/sub + await AddPubSubHandlersAsync(member).ConfigureAwait(false); + } + + public bool Remove(ConnectionGroupMember group) + { + while (true) + { + var arr = _members; + int index = -1; + for (int i = 0; i < arr.Length; i++) + { + if (ReferenceEquals(arr[i], group)) + { + index = i; + break; + } + } + + if (index == -1) return false; + var newArr = new ConnectionGroupMember[arr.Length - 1]; + if (index > 0) Array.Copy(arr, 0, newArr, 0, index); + if (index < newArr.Length) Array.Copy(arr, index + 1, newArr, index, newArr.Length - index); + if (Interlocked.CompareExchange(ref _members, newArr, arr) == arr) break; + } + + var muxer = group.ClearMultiplexer(); + RemoveEventHandlers(muxer); + OnConnectionChanged(GroupConnectionChangedEventArgs.ChangeType.Removed, group); + SelectPreferredGroup(); + muxer?.Dispose(); + return true; + } + + internal void OnHeartbeat() // for testing, to update latency etc + { + foreach (var member in _members) + { + member.Multiplexer.OnHeartbeat(); + } + } +} diff --git a/src/StackExchange.Redis/MultiGroupSubscriber.Tracking.cs b/src/StackExchange.Redis/MultiGroupSubscriber.Tracking.cs new file mode 100644 index 000000000..faece61d9 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupSubscriber.Tracking.cs @@ -0,0 +1,150 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupSubscriber +{ + // for the actual pub/sub tracking, we need to be more subtle; we need to maintain our *own* map of what is subscribed, + // and only forward messages from the currently live system. + public void Subscribe( + RedisChannel channel, + Action handler, + CommandFlags flags = CommandFlags.None) => parent.SubscribeToAll(channel, handler, flags, asyncState); + + public Task SubscribeAsync( + RedisChannel channel, + Action handler, + CommandFlags flags = CommandFlags.None) => parent.SubscribeToAllAsync(channel, handler, flags, asyncState); + + public ChannelMessageQueue Subscribe( + RedisChannel channel, + CommandFlags flags = CommandFlags.None) => throw new NotImplementedException("Soon"); + + public Task SubscribeAsync( + RedisChannel channel, + CommandFlags flags = CommandFlags.None) => throw new NotImplementedException("Soon"); + + // to do this we'd need to track the *filtered* subscriber per node per subscriber per channel + public void Unsubscribe( + RedisChannel channel, + Action? handler = null, + CommandFlags flags = CommandFlags.None) => throw new NotSupportedException("Removing individual pub/sub handlers from multi-group subscribers is not currently supported"); + + public Task UnsubscribeAsync( + RedisChannel channel, + Action? handler = null, + CommandFlags flags = CommandFlags.None) => throw new NotSupportedException("Removing individual pub/sub handlers from multi-group subscribers is not currently supported"); + + public void UnsubscribeAll(CommandFlags flags = CommandFlags.None) => parent.UnsubscribeFromAll(flags, asyncState); + + public Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None) => parent.UnsubscribeFromAllAsync(flags, asyncState); +} + +internal sealed partial class MultiGroupMultiplexer +{ + private static Action FilteredHandler(MultiGroupMultiplexer parent, ConnectionGroupMember active, Action handler) + { + // invoke a handler only if the active member is the one we expect + return (channel, value) => + { + if (ReferenceEquals(parent._active, active.Multiplexer)) + { + handler(channel, value); + } + }; + } + + private readonly struct HandlerTuple(RedisChannel channel, Action handler, CommandFlags flags, object? asyncState) + { + public readonly RedisChannel Channel = channel; + public readonly Action Handler = handler; + public readonly CommandFlags Flags = flags; + public readonly object? AsyncState = asyncState; + } + + private List _handlers = new(); + + public void SubscribeToAll(RedisChannel channel, Action handler, CommandFlags flags, object? asyncState) + { + lock (_handlers) + { + _handlers.Add(new HandlerTuple(channel, handler, flags, asyncState)); + } + foreach (var member in _members) + { + member.Multiplexer.GetSubscriber(asyncState).Subscribe(channel, FilteredHandler(this, member, handler), flags); + } + } + + public async Task SubscribeToAllAsync(RedisChannel channel, Action handler, CommandFlags flags, object? asyncState) + { + lock (_handlers) + { + _handlers.Add(new HandlerTuple(channel, handler, flags, asyncState)); + } + foreach (var member in _members) + { + await member.Multiplexer.GetSubscriber(asyncState).SubscribeAsync(channel, FilteredHandler(this, member, handler), flags); + } + } + + private HandlerTuple[] LeaseHandlers(out int count) + { + HandlerTuple[] lease; + lock (_handlers) + { + count = _handlers.Count; + if (count == 0) return []; + lease = ArrayPool.Shared.Rent(count); + _handlers.CopyTo(lease, 0); + } + + return lease; + } + + private async Task AddPubSubHandlersAsync(ConnectionGroupMember member) + { + // when adding a connection to an established group, add any missing pub/sub handlers + var lease = LeaseHandlers(out var count); + object? asyncState = null; + ISubscriber? subscriber = null; // try to reuse when possible + for (int i = 0; i < count; i++) + { + var tuple = lease[i]; + if (subscriber is null || tuple.AsyncState != asyncState) + { + asyncState = tuple.AsyncState; + subscriber = member.Multiplexer.GetSubscriber(asyncState); + } + await subscriber.SubscribeAsync(tuple.Channel, FilteredHandler(this, member, tuple.Handler), tuple.Flags); + } + ArrayPool.Shared.Return(lease); + } + + public void UnsubscribeFromAll(CommandFlags flags, object? asyncState) + { + lock (_handlers) + { + _handlers.Clear(); + } + foreach (var member in _members) + { + member.Multiplexer.GetSubscriber(asyncState).UnsubscribeAll(flags); + } + } + + public async Task UnsubscribeFromAllAsync(CommandFlags flags, object? asyncState) + { + lock (_handlers) + { + _handlers.Clear(); + } + foreach (var member in _members) + { + await member.Multiplexer.GetSubscriber(asyncState).UnsubscribeAllAsync(flags); + } + } +} diff --git a/src/StackExchange.Redis/MultiGroupSubscriber.cs b/src/StackExchange.Redis/MultiGroupSubscriber.cs new file mode 100644 index 000000000..4b62d5890 --- /dev/null +++ b/src/StackExchange.Redis/MultiGroupSubscriber.cs @@ -0,0 +1,41 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +internal sealed partial class MultiGroupSubscriber(MultiGroupMultiplexer parent, object? asyncState) : ISubscriber +{ + // for a lot of things, we can defer through to the active implementation + private ISubscriber GetActiveSubscriber() => parent.Active.GetSubscriber(asyncState); + + public IConnectionMultiplexer Multiplexer => parent; + + public bool TryWait(Task task) => GetActiveSubscriber().TryWait(task); + + public void Wait(Task task) => GetActiveSubscriber().Wait(task); + + public T Wait(Task task) => GetActiveSubscriber().Wait(task); + + public void WaitAll(params Task[] tasks) => GetActiveSubscriber().WaitAll(tasks); + + public TimeSpan Ping(CommandFlags flags = CommandFlags.None) => GetActiveSubscriber().Ping(flags); + + public Task PingAsync(CommandFlags flags = CommandFlags.None) => GetActiveSubscriber().PingAsync(flags); + + public EndPoint? IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None) => + GetActiveSubscriber().IdentifyEndpoint(channel, flags); + + public Task IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None) => + GetActiveSubscriber().IdentifyEndpointAsync(channel, flags); + + public bool IsConnected(RedisChannel channel = default) => GetActiveSubscriber().IsConnected(); + + public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) + => GetActiveSubscriber().Publish(channel, message, flags); + + public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) + => GetActiveSubscriber().PublishAsync(channel, message, flags); + + public EndPoint? SubscribedEndpoint(RedisChannel channel) => GetActiveSubscriber().SubscribedEndpoint(channel); +} diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index ab058de62..0394c015f 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,28 @@ #nullable enable +override StackExchange.Redis.ConnectionGroupMember.ToString() -> string! +StackExchange.Redis.ConnectionGroupMember +StackExchange.Redis.ConnectionGroupMember.ConnectionGroupMember(StackExchange.Redis.ConfigurationOptions! configuration, string! name = "") -> void +StackExchange.Redis.ConnectionGroupMember.ConnectionGroupMember(string! configuration, string! name = "") -> void +StackExchange.Redis.ConnectionGroupMember.IsConnected.get -> bool +StackExchange.Redis.ConnectionGroupMember.Latency.get -> System.TimeSpan +StackExchange.Redis.ConnectionGroupMember.Name.get -> string! +StackExchange.Redis.ConnectionGroupMember.Weight.get -> double +StackExchange.Redis.ConnectionGroupMember.Weight.set -> void +StackExchange.Redis.GroupConnectionChangedEventArgs +StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType +StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType.ActiveChanged = 5 -> StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType +StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType.Added = 1 -> StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType +StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType.Disconnected = 3 -> StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType +StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType.Reconnected = 4 -> StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType +StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType.Removed = 2 -> StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType +StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType.Unknown = 0 -> StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType +StackExchange.Redis.GroupConnectionChangedEventArgs.Group.get -> StackExchange.Redis.ConnectionGroupMember! +StackExchange.Redis.GroupConnectionChangedEventArgs.GroupConnectionChangedEventArgs(StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType type, StackExchange.Redis.ConnectionGroupMember! group, StackExchange.Redis.ConnectionGroupMember? previousGroup = null) -> void +StackExchange.Redis.GroupConnectionChangedEventArgs.PreviousGroup.get -> StackExchange.Redis.ConnectionGroupMember? +StackExchange.Redis.GroupConnectionChangedEventArgs.Type.get -> StackExchange.Redis.GroupConnectionChangedEventArgs.ChangeType +StackExchange.Redis.IConnectionGroup +StackExchange.Redis.IConnectionGroup.AddAsync(StackExchange.Redis.ConnectionGroupMember! group, System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! +StackExchange.Redis.IConnectionGroup.ConnectionChanged -> System.EventHandler? +StackExchange.Redis.IConnectionGroup.GetMembers() -> System.ReadOnlySpan +StackExchange.Redis.IConnectionGroup.Remove(StackExchange.Redis.ConnectionGroupMember! group) -> bool +static StackExchange.Redis.ConnectionMultiplexer.ConnectGroupAsync(StackExchange.Redis.ConnectionGroupMember![]! members, System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! diff --git a/src/StackExchange.Redis/ResultProcessor.cs b/src/StackExchange.Redis/ResultProcessor.cs index 0562d7a4c..7ec1df9bc 100644 --- a/src/StackExchange.Redis/ResultProcessor.cs +++ b/src/StackExchange.Redis/ResultProcessor.cs @@ -2841,17 +2841,11 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes } } - private sealed class TracerProcessor : ResultProcessor + private sealed class TracerProcessor(bool establishConnection) : ResultProcessor { - private readonly bool establishConnection; - - public TracerProcessor(bool establishConnection) - { - this.establishConnection = establishConnection; - } - public override bool SetResult(PhysicalConnection connection, Message message, in RawResult result) { + connection.BridgeCouldBeNull?.ServerEndPoint?.SetLatency(message.CreatedDateTime); connection.BridgeCouldBeNull?.Multiplexer.OnInfoMessage($"got '{result}' for '{message.CommandAndKey}' on '{connection}'"); var final = base.SetResult(connection, message, result); if (result.IsError) diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs index abe8d8afb..fb3c884b3 100644 --- a/src/StackExchange.Redis/ServerEndPoint.cs +++ b/src/StackExchange.Redis/ServerEndPoint.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; using System.Runtime.CompilerServices; @@ -1123,5 +1124,19 @@ internal bool HasPendingCallerFacingItems() if (interactive?.HasPendingCallerFacingItems() == true) return true; return subscription?.HasPendingCallerFacingItems() ?? false; } + + public void SetLatency(DateTime startTime) + { + try + { + LatencyTicks = ConnectionGroupMember.ToLatencyTicks(DateTime.UtcNow - startTime); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + } + + internal uint LatencyTicks { get; private set; } = uint.MaxValue; } } diff --git a/src/StackExchange.Redis/StackExchange.Redis.csproj b/src/StackExchange.Redis/StackExchange.Redis.csproj index 2c2e7702a..eeaf72c56 100644 --- a/src/StackExchange.Redis/StackExchange.Redis.csproj +++ b/src/StackExchange.Redis/StackExchange.Redis.csproj @@ -56,4 +56,10 @@ + + + + MultiGroupDatabase.cs + + \ No newline at end of file diff --git a/tests/StackExchange.Redis.Tests/InProcessTestServer.cs b/tests/StackExchange.Redis.Tests/InProcessTestServer.cs index 5ac222c0a..1b33ca4af 100644 --- a/tests/StackExchange.Redis.Tests/InProcessTestServer.cs +++ b/tests/StackExchange.Redis.Tests/InProcessTestServer.cs @@ -16,7 +16,7 @@ namespace StackExchange.Redis.Tests; public class InProcessTestServer : MemoryCacheRedisServer { private readonly ITestOutputHelper? _log; - public InProcessTestServer(ITestOutputHelper? log = null) + public InProcessTestServer(ITestOutputHelper? log = null, EndPoint? endpoint = null) : base(endpoint) { RedisVersion = RedisFeatures.v6_0_0; // for client to expect RESP3 _log = log; @@ -157,4 +157,13 @@ protected override void Dispose(bool disposing) if (disposing) _server.Dispose(); } */ + public void SetLatency(TimeSpan latency) => _latency = latency; + + private TimeSpan _latency = TimeSpan.Zero; + + protected override ValueTask ClientPauseAsync(RedisClient client) + { + var latency = _latency; + return latency <= TimeSpan.Zero ? base.ClientPauseAsync(client) : new(Task.Delay(latency)); + } } diff --git a/tests/StackExchange.Redis.Tests/MultiGroupTests/BasicMultiGroupTests.cs b/tests/StackExchange.Redis.Tests/MultiGroupTests/BasicMultiGroupTests.cs new file mode 100644 index 000000000..453e46d4a --- /dev/null +++ b/tests/StackExchange.Redis.Tests/MultiGroupTests/BasicMultiGroupTests.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using StackExchange.Redis.Tests.Helpers; +using Xunit; + +namespace StackExchange.Redis.Tests.MultiGroupTests; + +public class BasicMultiGroupTests(ITestOutputHelper log) +{ + protected TextWriter Log { get; } = new TextWriterOutputHelper(log); + + [Fact] + public async Task SelectByWeight() + { + EndPoint germany = new DnsEndPoint("germany", 6379); + EndPoint canada = new DnsEndPoint("canada", 6379); + EndPoint tokyo = new DnsEndPoint("tokyo", 6379); + + using var server0 = new InProcessTestServer(endpoint: germany); + using var server1 = new InProcessTestServer(endpoint: canada); + using var server2 = new InProcessTestServer(endpoint: tokyo); + + ConnectionGroupMember[] members = [ + new(server0.GetClientConfig()) { Weight = 2 }, + new(server1.GetClientConfig()) { Weight = 9 }, + new(server2.GetClientConfig()) { Weight = 3 }, + ]; + await using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members); + Assert.True(conn.IsConnected); + var typed = Assert.IsType(conn); + + // (R.4.1) If multiple member databases are configured, then I want to failover to the one with the highest weight. + var db = conn.GetDatabase(); + var ep = await db.IdentifyEndpointAsync(); + Assert.Equal(canada, ep); + + // change weight and update + members[1].Weight = 1; + typed.SelectPreferredGroup(); + ep = await db.IdentifyEndpointAsync(); + Assert.Equal(tokyo, ep); + + WriteLatency(conn); + } + + private void WriteLatency(IConnectionGroup conn) + { + var typed = Assert.IsType(conn); + foreach (var member in conn.GetMembers()) + { + log.WriteLine($"{member.Name}: {member.Latency.TotalMilliseconds}us"); + } + log.WriteLine($"Active: {typed.Active}"); + } + + [Fact] + public async Task SelectByLatency() + { + EndPoint germany = new DnsEndPoint("germany", 6379); + EndPoint canada = new DnsEndPoint("canada", 6379); + EndPoint tokyo = new DnsEndPoint("tokyo", 6379); + + using var server0 = new InProcessTestServer(endpoint: germany); + using var server1 = new InProcessTestServer(endpoint: canada); + using var server2 = new InProcessTestServer(endpoint: tokyo); + + ConnectionGroupMember[] members = [ + new(server0.GetClientConfig()), + new(server1.GetClientConfig()), + new(server2.GetClientConfig()), + ]; + await using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members); + conn.ConnectionChanged += (_, args) => log.WriteLine($"Connection changed: {args.Type}, from {args.PreviousGroup?.Name ?? "(nil)"} to {args.Group.Name}"); + + Assert.True(conn.IsConnected); + server0.SetLatency(TimeSpan.FromMilliseconds(10)); + server1.SetLatency(TimeSpan.Zero); + server2.SetLatency(TimeSpan.FromMilliseconds(15)); + var typed = Assert.IsType(conn); + typed.OnHeartbeat(); // update latencies + await Task.Delay(100); // allow time to settle + typed.SelectPreferredGroup(); + WriteLatency(typed); + + // (R.4.1) If multiple member databases are configured, then I want to failover to the one with the highest weight. + var db = conn.GetDatabase(); + var ep = await db.IdentifyEndpointAsync(); + Assert.Equal(canada, ep); + + // change latency and update + server0.SetLatency(TimeSpan.FromMilliseconds(10)); + server1.SetLatency(TimeSpan.FromMilliseconds(10)); + server2.SetLatency(TimeSpan.Zero); + typed.OnHeartbeat(); // update latencies + await Task.Delay(100); // allow time to settle + typed.SelectPreferredGroup(); + ep = await db.IdentifyEndpointAsync(); + WriteLatency(typed); + Assert.Equal(tokyo, ep); + } + + [Fact] + public async Task PubSubRouted() + { + EndPoint germany = new DnsEndPoint("germany", 6379); + EndPoint canada = new DnsEndPoint("canada", 6379); + EndPoint tokyo = new DnsEndPoint("tokyo", 6379); + + using var server0 = new InProcessTestServer(endpoint: germany); + using var server1 = new InProcessTestServer(endpoint: canada); + using var server2 = new InProcessTestServer(endpoint: tokyo); + + HashSet seen = []; + + void Seen(string source, RedisChannel channel, RedisValue value) + { + string message = $"[{source}] {channel}: {value}"; + lock (seen) + { + seen.Add(message); + } + } + + void Reset() + { + lock (seen) + { + seen.Clear(); + } + } + RedisChannel channel = RedisChannel.Literal("chan"); + var pub0 = (await server0.ConnectAsync()).GetSubscriber(); + await pub0.SubscribeAsync(channel, (x, y) => Seen(nameof(pub0), x, y)); + var pub1 = (await server1.ConnectAsync()).GetSubscriber(); + await pub1.SubscribeAsync(channel, (x, y) => Seen(nameof(pub1), x, y)); + var pub2 = (await server2.ConnectAsync()).GetSubscriber(); + await pub2.SubscribeAsync(channel, (x, y) => Seen(nameof(pub2), x, y)); + + ConnectionGroupMember[] members = [ + new(server0.GetClientConfig()) { Weight = 2 }, + new(server1.GetClientConfig()) { Weight = 9 }, + new(server2.GetClientConfig()) { Weight = 3 }, + ]; + await using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members); + Assert.True(conn.IsConnected); + var typed = Assert.IsType(conn); + var multi = conn.GetSubscriber(); + await multi.SubscribeAsync(channel, (x, y) => Seen(nameof(conn), x, y)); + + // (R.4.1) If multiple member databases are configured, then I want to failover to the one with the highest weight. + var db = conn.GetDatabase(); + var ep = await db.IdentifyEndpointAsync(); + Assert.Equal(canada, ep); + + // now publish via all 4 options, see what happens + Reset(); + await pub0.PublishAsync(channel, "abc"); + await pub1.PublishAsync(channel, "def"); + await pub2.PublishAsync(channel, "ghi"); + await multi.PublishAsync(channel, "jkl"); + await multi.PingAsync(); + + // we're expecting just canada, so: + Assert.Equal(6, seen.Count); + Assert.Contains("[pub0] chan: abc", seen); + Assert.Contains("[pub1] chan: def", seen); + Assert.Contains("[pub2] chan: ghi", seen); + Assert.Contains("[conn] chan: def", seen); // receives the message from pub1 + Assert.Contains("[conn] chan: jkl", seen); // receives the message from itself + Assert.Contains("[pub1] chan: jkl", seen); // received the message from the multi-group + + // change weight and update + members[1].Weight = 1; + typed.SelectPreferredGroup(); + ep = await db.IdentifyEndpointAsync(); + Assert.Equal(tokyo, ep); + + Reset(); + await pub0.PublishAsync(channel, "abc"); + await pub1.PublishAsync(channel, "def"); + await pub2.PublishAsync(channel, "ghi"); + await multi.PublishAsync(channel, "jkl"); + await multi.PingAsync(); + + // now we're expecting just tokyo, so: + Assert.Equal(6, seen.Count); + Assert.Contains("[pub0] chan: abc", seen); + Assert.Contains("[pub1] chan: def", seen); + Assert.Contains("[pub2] chan: ghi", seen); + Assert.Contains("[conn] chan: jkl", seen); // receives the message from pub2 + Assert.Contains("[conn] chan: jkl", seen); // receives the message from itself + Assert.Contains("[pub2] chan: jkl", seen); // received the message from the multi-group + } +} diff --git a/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj b/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj index 4227fedc3..c452bc677 100644 --- a/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj +++ b/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj @@ -32,6 +32,7 @@ + diff --git a/toys/StackExchange.Redis.Server/RespServer.cs b/toys/StackExchange.Redis.Server/RespServer.cs index b1611b087..98dcbac92 100644 --- a/toys/StackExchange.Redis.Server/RespServer.cs +++ b/toys/StackExchange.Redis.Server/RespServer.cs @@ -427,14 +427,14 @@ static void WritePrefix(PipeWriter output, char prefix) await output.FlushAsync().ConfigureAwait(false); } - private static bool TryParseRequest(Arena arena, ref ReadOnlySequence buffer, out RedisRequest request) + private static bool TryParseRequest(Arena arena, ref ReadOnlySequence buffer, out RawResult request) { var reader = new BufferReader(buffer); var raw = PhysicalConnection.TryParseResult(false, arena, in buffer, ref reader, false, null, true); if (raw.HasValue) { buffer = reader.SliceFromCurrent(); - request = new RedisRequest(raw); + request = raw; return true; } request = default; @@ -452,10 +452,32 @@ static async ValueTask Awaited(ValueTask write, TypedRedisValue response) response.Recycle(); return true; } - if (!buffer.IsEmpty && TryParseRequest(_arena, ref buffer, out var request)) + static async ValueTask AwaitedExec( + RespServer server, + ValueTask pause, + RedisClient client, + RawResult requestBuffer, + PipeWriter output) { - request = request.WithClient(client); + await pause.ConfigureAwait(false); TypedRedisValue response; + try { response = server.Execute(client, new RedisRequest(requestBuffer).WithClient(client)); } + finally + { + server._arena.Reset(); + client.ResetAfterRequest(); + } + + await WriteResponseAsync(client, output, response, client.Protocol).ConfigureAwait(false); + response.Recycle(); + return true; + } + if (!buffer.IsEmpty && TryParseRequest(_arena, ref buffer, out var requestBuffer)) + { + var pause = ClientPauseAsync(client); + if (!pause.IsCompletedSuccessfully) return AwaitedExec(this, pause, client, requestBuffer, output); + TypedRedisValue response; + var request = new RedisRequest(requestBuffer).WithClient(client); try { response = Execute(client, request); } finally { @@ -471,6 +493,8 @@ static async ValueTask Awaited(ValueTask write, TypedRedisValue response) return new ValueTask(false); } + protected virtual ValueTask ClientPauseAsync(RedisClient client) => default; + protected object ServerSyncLock => this; private long _totalCommandsProcesed, _totalErrorCount;