diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 679e796fd..1796710a7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -30,6 +30,7 @@ jobs: dotnet-version: | 6.0.x 8.0.x + 9.0.x - name: .NET Build run: dotnet build Build.csproj -c Release /p:CI=true - name: StackExchange.Redis.Tests diff --git a/Directory.Packages.props b/Directory.Packages.props index 12097728a..6d15ad199 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,25 +8,24 @@ - - + - + - + - + - + - - + + \ No newline at end of file diff --git a/StackExchange.Redis.sln b/StackExchange.Redis.sln index f59d5f6fc..18a30a9af 100644 --- a/StackExchange.Redis.sln +++ b/StackExchange.Redis.sln @@ -93,7 +93,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{73A5C363-CA1F-44C4-9A9B-EF791A76BA6A}" ProjectSection(SolutionItems) = preProject tests\.editorconfig = tests\.editorconfig - tests\Directory.Build.props = tests\Directory.Build.props tests\Directory.Build.targets = tests\Directory.Build.targets EndProjectSection EndProject diff --git a/appveyor.yml b/appveyor.yml index d9dd350af..b180c6544 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,6 +9,8 @@ install: choco install dotnet-6.0-sdk choco install dotnet-7.0-sdk + + choco install dotnet-9.0-sdk cd tests\RedisConfigs\3.0.503 diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 62edec160..c775f6cb3 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -8,7 +8,7 @@ Current package versions: ## Unreleased -No pending unreleased changes +- Package updates ([#2906 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2906)) ## 2.8.41 diff --git a/src/StackExchange.Redis/Interfaces/IServer.cs b/src/StackExchange.Redis/Interfaces/IServer.cs index fad2d4232..4971c7f18 100644 --- a/src/StackExchange.Redis/Interfaces/IServer.cs +++ b/src/StackExchange.Redis/Interfaces/IServer.cs @@ -89,7 +89,7 @@ public partial interface IServer : IRedis /// void ClientKill(EndPoint endpoint, CommandFlags flags = CommandFlags.None); - /// + /// Task ClientKillAsync(EndPoint endpoint, CommandFlags flags = CommandFlags.None); /// @@ -104,7 +104,7 @@ public partial interface IServer : IRedis /// long ClientKill(long? id = null, ClientType? clientType = null, EndPoint? endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None); - /// + /// Task ClientKillAsync(long? id = null, ClientType? clientType = null, EndPoint? endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None); /// @@ -475,17 +475,17 @@ public partial interface IServer : IRedis /// void Shutdown(ShutdownMode shutdownMode = ShutdownMode.Default, CommandFlags flags = CommandFlags.None); - /// + /// [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(ReplicaOfAsync) + " instead, this will be removed in 3.0.")] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] void SlaveOf(EndPoint master, CommandFlags flags = CommandFlags.None); - /// + /// [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(ReplicaOfAsync) + " instead, this will be removed in 3.0.")] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] Task SlaveOfAsync(EndPoint master, CommandFlags flags = CommandFlags.None); - /// + /// [Obsolete("Please use " + nameof(ReplicaOfAsync) + ", this will be removed in 3.0.")] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] void ReplicaOf(EndPoint master, CommandFlags flags = CommandFlags.None); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index b97bba73b..f45c29886 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -351,7 +351,7 @@ public Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = nul public Task ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? - Inner.ScriptEvaluateAsync(script, ToInner(keys), values, flags); + Inner.ScriptEvaluateAsync(script: script, keys: ToInner(keys), values: values, flags: flags); public Task ScriptEvaluateAsync(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) => // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? @@ -367,7 +367,7 @@ public Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? ke public Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? - Inner.ScriptEvaluateAsync(script, ToInner(keys), values, flags); + Inner.ScriptEvaluateAsync(script: script, keys: ToInner(keys), values: values, flags: flags); public Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => Inner.SetAddAsync(ToInner(key), values, flags); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs index 75d93d0f9..a19dd0b7a 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs @@ -338,7 +338,7 @@ public RedisResult ScriptEvaluate(byte[] hash, RedisKey[]? keys = null, RedisVal public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? - Inner.ScriptEvaluate(script, ToInner(keys), values, flags); + Inner.ScriptEvaluate(script: script, keys: ToInner(keys), values: values, flags: flags); public RedisResult ScriptEvaluate(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) => // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? diff --git a/src/StackExchange.Redis/LuaScript.cs b/src/StackExchange.Redis/LuaScript.cs index 8a9bdbcc1..7a99f635a 100644 --- a/src/StackExchange.Redis/LuaScript.cs +++ b/src/StackExchange.Redis/LuaScript.cs @@ -148,7 +148,7 @@ internal void ExtractParameters(object? ps, RedisKey? keyPrefix, out RedisKey[]? public RedisResult Evaluate(IDatabase db, object? ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) { ExtractParameters(ps, withKeyPrefix, out RedisKey[]? keys, out RedisValue[]? args); - return db.ScriptEvaluate(ExecutableScript, keys, args, flags); + return db.ScriptEvaluate(script: ExecutableScript, keys: keys, values: args, flags: flags); } /// @@ -161,7 +161,7 @@ public RedisResult Evaluate(IDatabase db, object? ps = null, RedisKey? withKeyPr public Task EvaluateAsync(IDatabaseAsync db, object? ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) { ExtractParameters(ps, withKeyPrefix, out RedisKey[]? keys, out RedisValue[]? args); - return db.ScriptEvaluateAsync(ExecutableScript, keys, args, flags); + return db.ScriptEvaluateAsync(script: ExecutableScript, keys: keys, values: args, flags: flags); } /// @@ -269,7 +269,7 @@ public RedisResult Evaluate(IDatabase db, object? ps = null, RedisKey? withKeyPr { Original.ExtractParameters(ps, withKeyPrefix, out RedisKey[]? keys, out RedisValue[]? args); - return db.ScriptEvaluate(ExecutableScript, keys, args, flags); + return db.ScriptEvaluate(script: ExecutableScript, keys: keys, values: args, flags: flags); } /// @@ -287,7 +287,7 @@ public Task EvaluateAsync(IDatabaseAsync db, object? ps = null, Red { Original.ExtractParameters(ps, withKeyPrefix, out RedisKey[]? keys, out RedisValue[]? args); - return db.ScriptEvaluateAsync(ExecutableScript, keys, args, flags); + return db.ScriptEvaluateAsync(script: ExecutableScript, keys: keys, values: args, flags: flags); } } } diff --git a/src/StackExchange.Redis/PhysicalConnection.cs b/src/StackExchange.Redis/PhysicalConnection.cs index c92290696..8a0bad393 100644 --- a/src/StackExchange.Redis/PhysicalConnection.cs +++ b/src/StackExchange.Redis/PhysicalConnection.cs @@ -1598,8 +1598,8 @@ internal async ValueTask ConnectedAsync(Socket? socket, ILogger? log, Sock catch (Exception ex) { Debug.WriteLine(ex.Message); - bridge.Multiplexer?.SetAuthSuspect(ex); - bridge.Multiplexer?.Logger?.LogError(ex, ex.Message); + bridge.Multiplexer.SetAuthSuspect(ex); + bridge.Multiplexer.Logger?.LogError(ex, ex.Message); throw; } log?.LogInformation($"TLS connection established successfully using protocol: {ssl.SslProtocol}"); diff --git a/src/StackExchange.Redis/ResultProcessor.cs b/src/StackExchange.Redis/ResultProcessor.cs index 47974b278..03e0a8577 100644 --- a/src/StackExchange.Redis/ResultProcessor.cs +++ b/src/StackExchange.Redis/ResultProcessor.cs @@ -234,11 +234,11 @@ public virtual bool SetResult(PhysicalConnection connection, Message message, in { if (result.StartsWith(CommonReplies.NOAUTH)) { - bridge?.Multiplexer?.SetAuthSuspect(new RedisServerException("NOAUTH Returned - connection has not yet authenticated")); + bridge?.Multiplexer.SetAuthSuspect(new RedisServerException("NOAUTH Returned - connection has not yet authenticated")); } else if (result.StartsWith(CommonReplies.WRONGPASS)) { - bridge?.Multiplexer?.SetAuthSuspect(new RedisServerException(result.ToString())); + bridge?.Multiplexer.SetAuthSuspect(new RedisServerException(result.ToString())); } var server = bridge?.ServerEndPoint; @@ -259,7 +259,7 @@ public virtual bool SetResult(PhysicalConnection connection, Message message, in // no point sending back to same server, and no point sending to a dead server if (!Equals(server?.EndPoint, endpoint)) { - if (bridge == null) + if (bridge is null) { // already toast } @@ -308,7 +308,7 @@ public virtual bool SetResult(PhysicalConnection connection, Message message, in { bridge?.Multiplexer.OnErrorMessage(server.EndPoint, err); } - bridge?.Multiplexer?.Trace("Completed with error: " + err + " (" + GetType().Name + ")", ToString()); + bridge?.Multiplexer.Trace("Completed with error: " + err + " (" + GetType().Name + ")", ToString()); if (unableToConnectError) { ConnectionFail(message, ConnectionFailureType.UnableToConnect, err); @@ -323,7 +323,7 @@ public virtual bool SetResult(PhysicalConnection connection, Message message, in bool coreResult = SetResultCore(connection, message, result); if (coreResult) { - bridge?.Multiplexer?.Trace("Completed with success: " + result.ToString() + " (" + GetType().Name + ")", ToString()); + bridge?.Multiplexer.Trace("Completed with success: " + result.ToString() + " (" + GetType().Name + ")", ToString()); } else { diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs index 55fe6cff3..1e32275d6 100644 --- a/src/StackExchange.Redis/ServerEndPoint.cs +++ b/src/StackExchange.Redis/ServerEndPoint.cs @@ -708,7 +708,7 @@ internal void OnFullyEstablished(PhysicalConnection connection, string source) } catch (Exception ex) { - connection.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); + connection?.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); } } diff --git a/src/StackExchange.Redis/StackExchange.Redis.csproj b/src/StackExchange.Redis/StackExchange.Redis.csproj index 17e34eab9..e1b428a36 100644 --- a/src/StackExchange.Redis/StackExchange.Redis.csproj +++ b/src/StackExchange.Redis/StackExchange.Redis.csproj @@ -16,8 +16,9 @@ - - + + + diff --git a/src/StackExchange.Redis/TextWriterLogger.cs b/src/StackExchange.Redis/TextWriterLogger.cs index 0582b70d3..4d8507b95 100644 --- a/src/StackExchange.Redis/TextWriterLogger.cs +++ b/src/StackExchange.Redis/TextWriterLogger.cs @@ -17,7 +17,12 @@ public TextWriterLogger(TextWriter writer, ILogger? wrapped) _wrapped = wrapped; } +#if NET8_0_OR_GREATER + public IDisposable? BeginScope(TState state) where TState : notnull => NothingDisposable.Instance; +#else public IDisposable BeginScope(TState state) => NothingDisposable.Instance; +#endif + public bool IsEnabled(LogLevel logLevel) => _writer is not null || _wrapped is not null; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs index f467aca24..e73225031 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs @@ -605,8 +605,8 @@ public void ScriptEvaluate_2() RedisValue[] values = Array.Empty(); RedisKey[] keys = new RedisKey[] { "a", "b" }; Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - prefixed.ScriptEvaluate("script", keys, values, CommandFlags.None); - mock.Received().ScriptEvaluate("script", Arg.Is(valid), values, CommandFlags.None); + prefixed.ScriptEvaluate(script: "script", keys: keys, values: values, flags: CommandFlags.None); + mock.Received().ScriptEvaluate(script: "script", keys: Arg.Is(valid), values: values, flags: CommandFlags.None); } [Fact] diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs index aebc8fc6d..e48227974 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs @@ -550,7 +550,7 @@ public async Task ScriptEvaluateAsync_2() RedisKey[] keys = new RedisKey[] { "a", "b" }; Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; await prefixed.ScriptEvaluateAsync("script", keys, values, CommandFlags.None); - await mock.Received().ScriptEvaluateAsync("script", Arg.Is(valid), values, CommandFlags.None); + await mock.Received().ScriptEvaluateAsync(script: "script", keys: Arg.Is(valid), values: values, flags: CommandFlags.None); } [Fact] diff --git a/tests/StackExchange.Redis.Tests/LockingTests.cs b/tests/StackExchange.Redis.Tests/LockingTests.cs index c2aa3611f..4f9dbd402 100644 --- a/tests/StackExchange.Redis.Tests/LockingTests.cs +++ b/tests/StackExchange.Redis.Tests/LockingTests.cs @@ -151,11 +151,11 @@ public async Task TakeLockAndExtend(TestMode testMode) Assert.Equal(right, await t4); if (withTran) Assert.False(await t5!, "5"); Assert.Equal(right, await t6); - var ttl = (await t7).Value.TotalSeconds; + var ttl = (await t7)!.Value.TotalSeconds; Assert.True(ttl > 0 && ttl <= 20, "7"); Assert.True(await t8, "8"); Assert.Equal(right, await t9); - ttl = (await t10).Value.TotalSeconds; + ttl = (await t10)!.Value.TotalSeconds; Assert.True(ttl > 50 && ttl <= 60, "10"); Assert.True(await t11, "11"); Assert.Null((string?)await t12); @@ -185,7 +185,7 @@ public async Task TestBasicLockNotTaken(TestMode testMode) } Assert.True(await taken!, "taken"); Assert.Equal("new-value", await newValue!); - var ttlValue = (await ttl!).Value.TotalSeconds; + var ttlValue = (await ttl!)!.Value.TotalSeconds; Assert.True(ttlValue >= 8 && ttlValue <= 10, "ttl"); Assert.Equal(0, errorCount); @@ -206,7 +206,7 @@ public async Task TestBasicLockTaken(TestMode testMode) Assert.False(await taken, "taken"); Assert.Equal("old-value", await newValue); - var ttlValue = (await ttl).Value.TotalSeconds; + var ttlValue = (await ttl)!.Value.TotalSeconds; Assert.True(ttlValue >= 18 && ttlValue <= 20, "ttl"); } } diff --git a/tests/StackExchange.Redis.Tests/LoggerTests.cs b/tests/StackExchange.Redis.Tests/LoggerTests.cs index 217864ba7..75077f9a9 100644 --- a/tests/StackExchange.Redis.Tests/LoggerTests.cs +++ b/tests/StackExchange.Redis.Tests/LoggerTests.cs @@ -69,7 +69,11 @@ public class TestWrapperLogger : ILogger public TestWrapperLogger(ILogger toWrap) => Inner = toWrap; +#if NET8_0_OR_GREATER + public IDisposable? BeginScope(TState state) where TState : notnull => Inner.BeginScope(state); +#else public IDisposable BeginScope(TState state) => Inner.BeginScope(state); +#endif public bool IsEnabled(LogLevel logLevel) => Inner.IsEnabled(logLevel); public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { @@ -86,7 +90,11 @@ private class TestMultiLogger : ILogger private readonly ILogger[] _loggers; public TestMultiLogger(params ILogger[] loggers) => _loggers = loggers; +#if NET8_0_OR_GREATER + public IDisposable? BeginScope(TState state) where TState : notnull => throw new NotImplementedException(); +#else public IDisposable BeginScope(TState state) => throw new NotImplementedException(); +#endif public bool IsEnabled(LogLevel logLevel) => true; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { @@ -106,7 +114,11 @@ private class TestLogger : ILogger public TestLogger(LogLevel logLevel, TextWriter output) => (_logLevel, _output) = (logLevel, output); +#if NET8_0_OR_GREATER + public IDisposable? BeginScope(TState state) where TState : notnull => throw new NotImplementedException(); +#else public IDisposable BeginScope(TState state) => throw new NotImplementedException(); +#endif public bool IsEnabled(LogLevel logLevel) => logLevel >= _logLevel; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { diff --git a/tests/StackExchange.Redis.Tests/RespProtocolTests.cs b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs index c508bd1c9..44932cf96 100644 --- a/tests/StackExchange.Redis.Tests/RespProtocolTests.cs +++ b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs @@ -180,7 +180,7 @@ public async Task CheckLuaResult(string script, RedisProtocol protocol, ResultTy db.HashSet("key", "b", 2); db.HashSet("key", "c", 3); } - var result = await db.ScriptEvaluateAsync(script, flags: CommandFlags.NoScriptCache); + var result = await db.ScriptEvaluateAsync(script: script, flags: CommandFlags.NoScriptCache); Assert.Equal(resp2, result.Resp2Type); Assert.Equal(resp3, result.Resp3Type); diff --git a/tests/StackExchange.Redis.Tests/ScriptingTests.cs b/tests/StackExchange.Redis.Tests/ScriptingTests.cs index 59bb0e608..f6de8044a 100644 --- a/tests/StackExchange.Redis.Tests/ScriptingTests.cs +++ b/tests/StackExchange.Redis.Tests/ScriptingTests.cs @@ -8,6 +8,9 @@ using Xunit; using Xunit.Abstractions; +// ReSharper disable UseAwaitUsing # for consistency with existing tests +// ReSharper disable MethodHasAsyncOverload # grandfathered existing usage +// ReSharper disable StringLiteralTypo # because of Lua scripts namespace StackExchange.Redis.Tests; [RunPerProtocol] @@ -27,7 +30,7 @@ private IConnectionMultiplexer GetScriptConn(bool allowAdmin = false) public void ClientScripting() { using var conn = GetScriptConn(); - _ = conn.GetDatabase().ScriptEvaluate("return redis.call('info','server')", null, null); + _ = conn.GetDatabase().ScriptEvaluate(script: "return redis.call('info','server')", keys: null, values: null); } [Fact] @@ -37,14 +40,14 @@ public async Task BasicScripting() var db = conn.GetDatabase(); var noCache = db.ScriptEvaluateAsync( - "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", - new RedisKey[] { "key1", "key2" }, - new RedisValue[] { "first", "second" }); + script: "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", + keys: new RedisKey[] { "key1", "key2" }, + values: new RedisValue[] { "first", "second" }); var cache = db.ScriptEvaluateAsync( - "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", - new RedisKey[] { "key1", "key2" }, - new RedisValue[] { "first", "second" }); - var results = (string[]?)await noCache; + script: "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", + keys: new RedisKey[] { "key1", "key2" }, + values: new RedisValue[] { "first", "second" }); + var results = (string[]?)(await noCache)!; Assert.NotNull(results); Assert.Equal(4, results.Length); Assert.Equal("key1", results[0]); @@ -52,7 +55,7 @@ public async Task BasicScripting() Assert.Equal("first", results[2]); Assert.Equal("second", results[3]); - results = (string[]?)await cache; + results = (string[]?)(await cache)!; Assert.NotNull(results); Assert.Equal(4, results.Length); Assert.Equal("key1", results[0]); @@ -69,16 +72,18 @@ public void KeysScripting() var db = conn.GetDatabase(); var key = Me(); db.StringSet(key, "bar", flags: CommandFlags.FireAndForget); - var result = (string?)db.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { key }, null); + var result = (string?)db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: new RedisKey[] { key }, values: null); Assert.Equal("bar", result); } [Fact] public async Task TestRandomThingFromForum() { - const string script = @"local currentVal = tonumber(redis.call('GET', KEYS[1])); - if (currentVal <= 0 ) then return 1 elseif (currentVal - (tonumber(ARGV[1])) < 0 ) then return 0 end; - return redis.call('INCRBY', KEYS[1], -tonumber(ARGV[1]));"; + const string Script = """ + local currentVal = tonumber(redis.call('GET', KEYS[1])); + if (currentVal <= 0 ) then return 1 elseif (currentVal - (tonumber(ARGV[1])) < 0 ) then return 0 end; + return redis.call('INCRBY', KEYS[1], -tonumber(ARGV[1])); + """; using var conn = GetScriptConn(); @@ -88,18 +93,18 @@ public async Task TestRandomThingFromForum() db.StringSet(prefix + "B", "5", flags: CommandFlags.FireAndForget); db.StringSet(prefix + "C", "10", flags: CommandFlags.FireAndForget); - var a = db.ScriptEvaluateAsync(script, new RedisKey[] { prefix + "A" }, new RedisValue[] { 6 }).ForAwait(); - var b = db.ScriptEvaluateAsync(script, new RedisKey[] { prefix + "B" }, new RedisValue[] { 6 }).ForAwait(); - var c = db.ScriptEvaluateAsync(script, new RedisKey[] { prefix + "C" }, new RedisValue[] { 6 }).ForAwait(); + var a = db.ScriptEvaluateAsync(script: Script, keys: new RedisKey[] { prefix + "A" }, values: new RedisValue[] { 6 }).ForAwait(); + var b = db.ScriptEvaluateAsync(script: Script, keys: new RedisKey[] { prefix + "B" }, values: new RedisValue[] { 6 }).ForAwait(); + var c = db.ScriptEvaluateAsync(script: Script, keys: new RedisKey[] { prefix + "C" }, values: new RedisValue[] { 6 }).ForAwait(); - var vals = await db.StringGetAsync(new RedisKey[] { prefix + "A", prefix + "B", prefix + "C" }).ForAwait(); + var values = await db.StringGetAsync(new RedisKey[] { prefix + "A", prefix + "B", prefix + "C" }).ForAwait(); Assert.Equal(1, (long)await a); // exit code when current val is non-positive Assert.Equal(0, (long)await b); // exit code when result would be negative Assert.Equal(4, (long)await c); // 10 - 6 = 4 - Assert.Equal("0", vals[0]); - Assert.Equal("5", vals[1]); - Assert.Equal("4", vals[2]); + Assert.Equal("0", values[0]); + Assert.Equal("5", values[1]); + Assert.Equal("4", values[2]); } [Fact] @@ -118,9 +123,9 @@ public async Task MultiIncrWithoutReplies() // run the script, passing "a", "b", "c", "c" to // increment a & b by 1, c twice var result = db.ScriptEvaluateAsync( - "for i,key in ipairs(KEYS) do redis.call('incr', key) end", - new RedisKey[] { prefix + "a", prefix + "b", prefix + "c", prefix + "c" }, // <== aka "KEYS" in the script - null).ForAwait(); // <== aka "ARGV" in the script + script: "for i,key in ipairs(KEYS) do redis.call('incr', key) end", + keys: new RedisKey[] { prefix + "a", prefix + "b", prefix + "c", prefix + "c" }, // <== aka "KEYS" in the script + values: null).ForAwait(); // <== aka "ARGV" in the script // check the incremented values var a = db.StringGetAsync(prefix + "a").ForAwait(); @@ -151,9 +156,9 @@ public async Task MultiIncrByWithoutReplies() // run the script, passing "a", "b", "c" and 1,2,3 // increment a & b by 1, c twice var result = db.ScriptEvaluateAsync( - "for i,key in ipairs(KEYS) do redis.call('incrby', key, ARGV[i]) end", - new RedisKey[] { prefix + "a", prefix + "b", prefix + "c" }, // <== aka "KEYS" in the script - new RedisValue[] { 1, 1, 2 }).ForAwait(); // <== aka "ARGV" in the script + script: "for i,key in ipairs(KEYS) do redis.call('incrby', key, ARGV[i]) end", + keys: new RedisKey[] { prefix + "a", prefix + "b", prefix + "c" }, // <== aka "KEYS" in the script + values: new RedisValue[] { 1, 1, 2 }).ForAwait(); // <== aka "ARGV" in the script // check the incremented values var a = db.StringGetAsync(prefix + "a").ForAwait(); @@ -174,7 +179,7 @@ public void DisableStringInference() var db = conn.GetDatabase(); var key = Me(); db.StringSet(key, "bar", flags: CommandFlags.FireAndForget); - var result = (byte[]?)db.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { key }); + var result = (byte[]?)db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: new RedisKey[] { key }); Assert.NotNull(result); Assert.Equal("bar", Encoding.UTF8.GetString(result)); } @@ -188,16 +193,16 @@ public void FlushDetection() var db = conn.GetDatabase(); var key = Me(); db.StringSet(key, "bar", flags: CommandFlags.FireAndForget); - var result = (string?)db.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { key }, null); + var result = (string?)db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: new RedisKey[] { key }, values: null); Assert.Equal("bar", result); // now cause all kinds of problems GetServer(conn).ScriptFlush(); // expect this one to fail just work fine (self-fix) - db.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { key }, null); + db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: new RedisKey[] { key }, values: null); - result = (string?)db.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { key }, null); + result = (string?)db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: new RedisKey[] { key }, values: null); Assert.Equal("bar", result); } @@ -241,11 +246,11 @@ public void NonAsciiScripts() { using var conn = GetScriptConn(); - const string evil = "return '僕'"; + const string Evil = "return '僕'"; var db = conn.GetDatabase(); - GetServer(conn).ScriptLoad(evil); + GetServer(conn).ScriptLoad(Evil); - var result = (string?)db.ScriptEvaluate(evil, null, null); + var result = (string?)db.ScriptEvaluate(script: Evil, keys: null, values: null); Assert.Equal("僕", result); } @@ -258,7 +263,7 @@ await Assert.ThrowsAsync(async () => var db = conn.GetDatabase(); try { - await db.ScriptEvaluateAsync("return redis.error_reply('oops')", null, null).ForAwait(); + await db.ScriptEvaluateAsync(script: "return redis.error_reply('oops')", keys: null, values: null).ForAwait(); } catch (AggregateException ex) { @@ -280,7 +285,7 @@ public void ScriptThrowsErrorInsideTransaction() var tran = db.CreateTransaction(); { var a = tran.StringIncrementAsync(key); - var b = tran.ScriptEvaluateAsync("return redis.error_reply('oops')", null, null); + var b = tran.ScriptEvaluateAsync(script: "return redis.error_reply('oops')", keys: null, values: null); var c = tran.StringIncrementAsync(key); var complete = tran.ExecuteAsync(); @@ -322,10 +327,10 @@ public async Task ChangeDbInScript() Log("Key: " + key); var db = conn.GetDatabase(2); var evalResult = db.ScriptEvaluateAsync( - @"redis.call('select', 1) + script: @"redis.call('select', 1) return redis.call('get','" + key + "')", - null, - null); + keys: null, + values: null); var getResult = db.StringGetAsync(key); Assert.Equal("db 1", (string?)await evalResult); @@ -345,10 +350,10 @@ public async Task ChangeDbInTranScript() var db = conn.GetDatabase(2); var tran = db.CreateTransaction(); var evalResult = tran.ScriptEvaluateAsync( - @"redis.call('select', 1) + script: @"redis.call('select', 1) return redis.call('get','" + key + "')", - null, - null); + keys: null, + values: null); var getResult = tran.StringGetAsync(key); Assert.True(tran.Execute()); @@ -369,16 +374,16 @@ public void TestBasicScripting() db.HashSet(key, "id", 123, flags: CommandFlags.FireAndForget); var wasSet = (bool)db.ScriptEvaluate( - "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", - new[] { key }, - new[] { newId }); + script: "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", + keys: new[] { key }, + values: new[] { newId }); Assert.True(wasSet); wasSet = (bool)db.ScriptEvaluate( - "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", - new[] { key }, - new[] { newId }); + script: "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", + keys: new[] { key }, + values: new[] { newId }); Assert.False(wasSet); } @@ -394,16 +399,16 @@ public async Task CheckLoads(bool async) // the flush to drop the local cache - assume it is a surprise!) var server = conn0.GetServer(TestConfig.Current.PrimaryServerAndPort); var db = conn1.GetDatabase(); - const string script = "return 1;"; + const string Script = "return 1;"; // start empty server.ScriptFlush(); - Assert.False(server.ScriptExists(script)); + Assert.False(server.ScriptExists(Script)); // run once, causes to be cached Assert.True(await EvaluateScript()); - Assert.True(server.ScriptExists(script)); + Assert.True(server.ScriptExists(Script)); // can run again Assert.True(await EvaluateScript()); @@ -411,7 +416,7 @@ public async Task CheckLoads(bool async) // ditch the scripts; should no longer exist db.Ping(); server.ScriptFlush(); - Assert.False(server.ScriptExists(script)); + Assert.False(server.ScriptExists(Script)); db.Ping(); // just works; magic @@ -421,13 +426,13 @@ public async Task CheckLoads(bool async) Assert.True(await EvaluateScript()); // which will cause it to be cached - Assert.True(server.ScriptExists(script)); + Assert.True(server.ScriptExists(Script)); async Task EvaluateScript() { return async ? - (bool)await db!.ScriptEvaluateAsync(script) : - (bool)db!.ScriptEvaluate(script); + (bool)await db.ScriptEvaluateAsync(script: Script) : + (bool)db.ScriptEvaluate(script: Script); } } @@ -445,25 +450,25 @@ public void CompareScriptToDirect() db.Ping(); // k, we're all up to date now; clean db, minimal script cache // we're using a pipeline here, so send 1000 messages, but for timing: only care about the last - const int LOOP = 5000; + const int Loop = 5000; RedisKey key = Me(); RedisKey[] keys = new[] { key }; // script takes an array // run via script db.KeyDelete(key, CommandFlags.FireAndForget); var watch = Stopwatch.StartNew(); - for (int i = 1; i < LOOP; i++) // the i=1 is to do all-but-one + for (int i = 1; i < Loop; i++) // the i=1 is to do all-but-one { - db.ScriptEvaluate(Script, keys, flags: CommandFlags.FireAndForget); + db.ScriptEvaluate(script: Script, keys: keys, flags: CommandFlags.FireAndForget); } - var scriptResult = db.ScriptEvaluate(Script, keys); // last one we wait for (no F+F) + var scriptResult = db.ScriptEvaluate(script: Script, keys: keys); // last one we wait for (no F+F) watch.Stop(); TimeSpan scriptTime = watch.Elapsed; // run via raw op db.KeyDelete(key, CommandFlags.FireAndForget); watch = Stopwatch.StartNew(); - for (int i = 1; i < LOOP; i++) // the i=1 is to do all-but-one + for (int i = 1; i < Loop; i++) // the i=1 is to do all-but-one { db.StringIncrement(key, flags: CommandFlags.FireAndForget); } @@ -471,8 +476,8 @@ public void CompareScriptToDirect() watch.Stop(); TimeSpan directTime = watch.Elapsed; - Assert.Equal(LOOP, (long)scriptResult); - Assert.Equal(LOOP, directResult); + Assert.Equal(Loop, (long)scriptResult); + Assert.Equal(Loop, directResult); Log("script: {0}ms; direct: {1}ms", scriptTime.TotalMilliseconds, directTime.TotalMilliseconds); } @@ -486,7 +491,7 @@ public void TestCallByHash() var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); server.ScriptFlush(); - byte[]? hash = server.ScriptLoad(Script); + byte[] hash = server.ScriptLoad(Script); Assert.NotNull(hash); var db = conn.GetDatabase(); @@ -497,7 +502,7 @@ public void TestCallByHash() string hexHash = string.Concat(hash.Select(x => x.ToString("X2"))); Assert.Equal("2BAB3B661081DB58BD2341920E0BA7CF5DC77B25", hexHash); - db.ScriptEvaluate(hexHash, keys, flags: CommandFlags.FireAndForget); + db.ScriptEvaluate(script: hexHash, keys: keys, flags: CommandFlags.FireAndForget); db.ScriptEvaluate(hash, keys, flags: CommandFlags.FireAndForget); var count = (int)db.StringGet(keys)[0]; @@ -571,39 +576,39 @@ public void SimpleRawScriptEvaluate() // Scopes for repeated use { - var val = db.ScriptEvaluate(Script, values: new RedisValue[] { "hello" }); + var val = db.ScriptEvaluate(script: Script, values: new RedisValue[] { "hello" }); Assert.Equal("hello", (string?)val); } { - var val = db.ScriptEvaluate(Script, values: new RedisValue[] { 123 }); + var val = db.ScriptEvaluate(script: Script, values: new RedisValue[] { 123 }); Assert.Equal(123, (int)val); } { - var val = db.ScriptEvaluate(Script, values: new RedisValue[] { 123L }); + var val = db.ScriptEvaluate(script: Script, values: new RedisValue[] { 123L }); Assert.Equal(123L, (long)val); } { - var val = db.ScriptEvaluate(Script, values: new RedisValue[] { 1.1 }); + var val = db.ScriptEvaluate(script: Script, values: new RedisValue[] { 1.1 }); Assert.Equal(1.1, (double)val); } { - var val = db.ScriptEvaluate(Script, values: new RedisValue[] { true }); + var val = db.ScriptEvaluate(script: Script, values: new RedisValue[] { true }); Assert.True((bool)val); } { - var val = db.ScriptEvaluate(Script, values: new RedisValue[] { new byte[] { 4, 5, 6 } }); + var val = db.ScriptEvaluate(script: Script, values: new RedisValue[] { new byte[] { 4, 5, 6 } }); var valArray = (byte[]?)val; Assert.NotNull(valArray); Assert.True(new byte[] { 4, 5, 6 }.SequenceEqual(valArray)); } { - var val = db.ScriptEvaluate(Script, values: new RedisValue[] { new ReadOnlyMemory(new byte[] { 4, 5, 6 }) }); + var val = db.ScriptEvaluate(script: Script, values: new RedisValue[] { new ReadOnlyMemory(new byte[] { 4, 5, 6 }) }); var valArray = (byte[]?)val; Assert.NotNull(valArray); Assert.True(new byte[] { 4, 5, 6 }.SequenceEqual(valArray)); @@ -786,7 +791,7 @@ public void PurgeLuaScriptOnFinalize() Assert.Equal(0, LuaScript.GetCachedScriptCount()); // This has to be a separate method to guarantee that the created LuaScript objects go out of scope, - // and are thus available to be GC'd + // and are thus available to be garbage collected. PurgeLuaScriptOnFinalizeImpl(Script); CollectGarbage(); @@ -797,7 +802,7 @@ public void PurgeLuaScriptOnFinalize() } [Fact] - public void IDatabaseLuaScriptConvenienceMethods() + public void DatabaseLuaScriptConvenienceMethods() { using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); @@ -818,7 +823,7 @@ public void IDatabaseLuaScriptConvenienceMethods() } [Fact] - public void IServerLuaScriptConvenienceMethods() + public void ServerLuaScriptConvenienceMethods() { using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); @@ -978,14 +983,14 @@ public void ScriptWithKeyPrefixViaArrays() var p = conn.GetDatabase().WithKeyPrefix("prefix/"); - const string script = @" + const string Script = @" local arr = {}; arr[1] = ARGV[1]; arr[2] = KEYS[1]; arr[3] = ARGV[2]; return arr; "; - var result = (RedisValue[]?)p.ScriptEvaluate(script, new RedisKey[] { "def" }, new RedisValue[] { "abc", 123 }); + var result = (RedisValue[]?)p.ScriptEvaluate(script: Script, keys: new RedisKey[] { "def" }, values: new RedisValue[] { "abc", 123 }); Assert.NotNull(result); Assert.Equal("abc", result[0]); Assert.Equal("prefix/def", result[1]); @@ -1002,7 +1007,7 @@ public void ScriptWithKeyPrefixCompare() LuaScript lua = LuaScript.Prepare("return {@k, @s, @v}"); var viaArgs = (RedisValue[]?)p.ScriptEvaluate(lua, args); - var viaArr = (RedisValue[]?)p.ScriptEvaluate("return {KEYS[1], ARGV[1], ARGV[2]}", new[] { args.k }, new RedisValue[] { args.s, args.v }); + var viaArr = (RedisValue[]?)p.ScriptEvaluate(script: "return {KEYS[1], ARGV[1], ARGV[2]}", keys: new[] { args.k }, values: new RedisValue[] { args.s, args.v }); Assert.NotNull(viaArr); Assert.NotNull(viaArgs); Assert.Equal(string.Join(",", viaArr), string.Join(",", viaArgs)); @@ -1035,10 +1040,10 @@ private static void TestNullArray(RedisResult? value) Assert.Null((bool[]?)value); Assert.Null((long[]?)value); Assert.Null((ulong[]?)value); - Assert.Null((string[]?)value); + Assert.Null((string[]?)value!); Assert.Null((int[]?)value); Assert.Null((double[]?)value); - Assert.Null((byte[][]?)value); + Assert.Null((byte[][]?)value!); Assert.Null((RedisResult[]?)value); } @@ -1054,8 +1059,8 @@ public void TestEvalReadonly() var db = conn.GetDatabase(); string script = "return KEYS[1]"; - RedisKey[] keys = new RedisKey[1] { "key1" }; - RedisValue[] values = new RedisValue[1] { "first" }; + RedisKey[] keys = { "key1" }; + RedisValue[] values = { "first" }; var result = db.ScriptEvaluateReadOnly(script, keys, values); Assert.Equal("key1", result.ToString()); @@ -1068,8 +1073,8 @@ public async Task TestEvalReadonlyAsync() var db = conn.GetDatabase(); string script = "return KEYS[1]"; - RedisKey[] keys = new RedisKey[1] { "key1" }; - RedisValue[] values = new RedisValue[1] { "first" }; + RedisKey[] keys = { "key1" }; + RedisValue[] values = { "first" }; var result = await db.ScriptEvaluateReadOnlyAsync(script, keys, values); Assert.Equal("key1", result.ToString()); @@ -1081,7 +1086,7 @@ public void TestEvalShaReadOnly() using var conn = GetScriptConn(); var db = conn.GetDatabase(); db.StringSet("foo", "bar"); - db.ScriptEvaluate("return redis.call('get','foo')"); + db.ScriptEvaluate(script: "return redis.call('get','foo')"); // Create a SHA1 hash of the script: 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 SHA1 sha1Hash = SHA1.Create(); @@ -1097,7 +1102,7 @@ public async Task TestEvalShaReadOnlyAsync() using var conn = GetScriptConn(); var db = conn.GetDatabase(); db.StringSet("foo", "bar"); - db.ScriptEvaluate("return redis.call('get','foo')"); + db.ScriptEvaluate(script: "return redis.call('get','foo')"); // Create a SHA1 hash of the script: 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 SHA1 sha1Hash = SHA1.Create(); diff --git a/tests/StackExchange.Redis.Tests/SortedSetTests.cs b/tests/StackExchange.Redis.Tests/SortedSetTests.cs index 9bcd02e2e..7ba86fcf6 100644 --- a/tests/StackExchange.Redis.Tests/SortedSetTests.cs +++ b/tests/StackExchange.Redis.Tests/SortedSetTests.cs @@ -338,7 +338,7 @@ public void SortedSetRangeViaScript() db.KeyDelete(key, CommandFlags.FireAndForget); db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); - var result = db.ScriptEvaluate("return redis.call('ZRANGE', KEYS[1], 0, -1, 'WITHSCORES')", new RedisKey[] { key }); + var result = db.ScriptEvaluate(script: "return redis.call('ZRANGE', KEYS[1], 0, -1, 'WITHSCORES')", keys: new RedisKey[] { key }); AssertFlatArrayEntries(result); }