From e3c71732a13c5b958825113630faeb929c56b62d Mon Sep 17 00:00:00 2001 From: shacharPash Date: Sun, 22 May 2022 11:10:15 +0300 Subject: [PATCH 1/8] support EVAL_RO & EVALSHA_RO commands - work in progress --- src/StackExchange.Redis/Interfaces/IServer.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/StackExchange.Redis/Interfaces/IServer.cs b/src/StackExchange.Redis/Interfaces/IServer.cs index 3d9eb8065..4bfd0fb9a 100644 --- a/src/StackExchange.Redis/Interfaces/IServer.cs +++ b/src/StackExchange.Redis/Interfaces/IServer.cs @@ -230,6 +230,67 @@ public partial interface IServer : IRedis /// Task ExecuteAsync(string command, ICollection args, CommandFlags flags = CommandFlags.None); + /// + /// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a Lua script against the server. + /// + /// The script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// , + /// + /// + RedisResult ScriptEvaluateRO(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + RedisResult ScriptEvaluateROAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + + /// + /// Read-only variant of the EVALSHA command that cannot execute commands that modify data, Execute a Lua script against the server using just the SHA1 hash. + /// + /// The hash of the script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + RedisResult ScriptEvaluateRO(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + RedisResult ScriptEvaluateROAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + /// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a lua script against the server, using previously prepared script. + /// Named parameters, if any, are provided by the `parameters` object. + /// + /// The script to execute. + /// The parameters to pass to the script. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + RedisResult ScriptEvaluateRO(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + + /// + RedisResult ScriptEvaluateROAsync(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + + /// + /// Read-only variant of the EVALSHA command that cannot execute commands that modify data, Execute a lua script against the server, using previously prepared and loaded script. + /// This method sends only the SHA1 hash of the lua script to Redis. + /// Named parameters, if any, are provided by the `parameters` object. + /// + /// The already-loaded script to execute. + /// The parameters to pass to the script. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + RedisResult ScriptEvaluateRO(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + + /// + RedisResult ScriptEvaluateROAsync(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + /// /// Delete all the keys of all databases on the server. /// From 27ede06057ccf2fc2bc6734433f08052594d02cc Mon Sep 17 00:00:00 2001 From: shacharPash Date: Tue, 21 Jun 2022 18:13:50 +0300 Subject: [PATCH 2/8] Support EVAL_RO and EVALSHA_RO commands --- src/StackExchange.Redis/Enums/RedisCommand.cs | 2 + .../Interfaces/IDatabase.cs | 25 ++++++ .../Interfaces/IDatabaseAsync.cs | 25 ++++++ src/StackExchange.Redis/Interfaces/IServer.cs | 61 --------------- src/StackExchange.Redis/PublicAPI.Shipped.txt | 4 + src/StackExchange.Redis/RedisDatabase.cs | 78 ++++++++++++++----- 6 files changed, 113 insertions(+), 82 deletions(-) diff --git a/src/StackExchange.Redis/Enums/RedisCommand.cs b/src/StackExchange.Redis/Enums/RedisCommand.cs index 8587ceedf..9467f7687 100644 --- a/src/StackExchange.Redis/Enums/RedisCommand.cs +++ b/src/StackExchange.Redis/Enums/RedisCommand.cs @@ -35,6 +35,8 @@ internal enum RedisCommand ECHO, EVAL, EVALSHA, + EVAL_RO, + EVALSHA_RO, EXEC, EXISTS, EXPIRE, diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs index ee4dc0c4b..c974bb98c 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabase.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -1298,6 +1298,31 @@ public interface IDatabase : IRedis, IDatabaseAsync /// RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + /// + /// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a Lua script against the server. + /// + /// The script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// , + /// + /// + RedisResult ScriptEvaluateRO(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + /// Read-only variant of the EVALSHA command that cannot execute commands that modify data, Execute a Lua script against the server using just the SHA1 hash. + /// + /// The hash of the script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + RedisResult ScriptEvaluateRO(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + /// /// Add the specified member to the set stored at key. /// Specified members that are already a member of this set are ignored. diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs index b3950743e..0a2ca7ec6 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -1274,6 +1274,31 @@ public interface IDatabaseAsync : IRedisAsync /// Task ScriptEvaluateAsync(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + /// + /// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a Lua script against the server. + /// + /// The script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// , + /// + /// + Task ScriptEvaluateROAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + /// Read-only variant of the EVALSHA command that cannot execute commands that modify data, Execute a Lua script against the server using just the SHA1 hash. + /// + /// The hash of the script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + Task ScriptEvaluateROAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + /// /// Add the specified member to the set stored at key. /// Specified members that are already a member of this set are ignored. diff --git a/src/StackExchange.Redis/Interfaces/IServer.cs b/src/StackExchange.Redis/Interfaces/IServer.cs index 4bfd0fb9a..3d9eb8065 100644 --- a/src/StackExchange.Redis/Interfaces/IServer.cs +++ b/src/StackExchange.Redis/Interfaces/IServer.cs @@ -230,67 +230,6 @@ public partial interface IServer : IRedis /// Task ExecuteAsync(string command, ICollection args, CommandFlags flags = CommandFlags.None); - /// - /// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a Lua script against the server. - /// - /// The script to execute. - /// The keys to execute against. - /// The values to execute against. - /// The flags to use for this operation. - /// A dynamic representation of the script's result. - /// - /// , - /// - /// - RedisResult ScriptEvaluateRO(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); - - /// - RedisResult ScriptEvaluateROAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); - - - /// - /// Read-only variant of the EVALSHA command that cannot execute commands that modify data, Execute a Lua script against the server using just the SHA1 hash. - /// - /// The hash of the script to execute. - /// The keys to execute against. - /// The values to execute against. - /// The flags to use for this operation. - /// A dynamic representation of the script's result. - /// - RedisResult ScriptEvaluateRO(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); - - /// - RedisResult ScriptEvaluateROAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); - - /// - /// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a lua script against the server, using previously prepared script. - /// Named parameters, if any, are provided by the `parameters` object. - /// - /// The script to execute. - /// The parameters to pass to the script. - /// The flags to use for this operation. - /// A dynamic representation of the script's result. - /// - RedisResult ScriptEvaluateRO(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); - - /// - RedisResult ScriptEvaluateROAsync(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); - - /// - /// Read-only variant of the EVALSHA command that cannot execute commands that modify data, Execute a lua script against the server, using previously prepared and loaded script. - /// This method sends only the SHA1 hash of the lua script to Redis. - /// Named parameters, if any, are provided by the `parameters` object. - /// - /// The already-loaded script to execute. - /// The parameters to pass to the script. - /// The flags to use for this operation. - /// A dynamic representation of the script's result. - /// - RedisResult ScriptEvaluateRO(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); - - /// - RedisResult ScriptEvaluateROAsync(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); - /// /// Delete all the keys of all databases on the server. /// diff --git a/src/StackExchange.Redis/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI.Shipped.txt index 0b4924949..b2ec4bb48 100644 --- a/src/StackExchange.Redis/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI.Shipped.txt @@ -593,6 +593,8 @@ StackExchange.Redis.IDatabase.ScriptEvaluate(byte[]! hash, StackExchange.Redis.R StackExchange.Redis.IDatabase.ScriptEvaluate(StackExchange.Redis.LoadedLuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabase.ScriptEvaluate(StackExchange.Redis.LuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabase.ScriptEvaluate(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluateRO(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluateRO(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabase.SetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool StackExchange.Redis.IDatabase.SetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long StackExchange.Redis.IDatabase.SetCombine(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! @@ -818,6 +820,8 @@ StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(byte[]! hash, StackExchan StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(StackExchange.Redis.LoadedLuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(StackExchange.Redis.LuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateROAsync(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateROAsync(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.SetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.SetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.SetCombineAndStoreAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 7a4e7af98..044a6cc3d 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -1493,20 +1493,6 @@ public Task PublishAsync(RedisChannel channel, RedisValue message, Command return ExecuteAsync(msg, ResultProcessor.Int64); } - public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) - { - var msg = new ScriptEvalMessage(Database, flags, script, keys, values); - try - { - return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); - } - catch (RedisServerException) when (msg.IsScriptUnavailable) - { - // could be a NOSCRIPT; for a sync call, we can re-issue that without problem - return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); - } - } - public RedisResult Execute(string command, params object[] args) => Execute(command, args, CommandFlags.None); @@ -1525,9 +1511,24 @@ public Task ExecuteAsync(string command, ICollection? args, return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } + public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + try + { + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + catch (RedisServerException) when (msg.IsScriptUnavailable) + { + // could be a NOSCRIPT; for a sync call, we can re-issue that without problem + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + } + public RedisResult ScriptEvaluate(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { - var msg = new ScriptEvalMessage(Database, flags, hash, keys, values); + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -1543,13 +1544,14 @@ public RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = n public Task ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { - var msg = new ScriptEvalMessage(Database, flags, script, keys, values); + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } public Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { - var msg = new ScriptEvalMessage(Database, flags, hash, keys, values); + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -1563,6 +1565,40 @@ public Task ScriptEvaluateAsync(LoadedLuaScript script, object? par return script.EvaluateAsync(this, parameters, null, flags); } + public RedisResult ScriptEvaluateRO(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + try + { + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + catch (RedisServerException) when (msg.IsScriptUnavailable) + { + // could be a NOSCRIPT; for a sync call, we can re-issue that without problem + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + } + + public RedisResult ScriptEvaluateRO(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public Task ScriptEvaluateROAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public Task ScriptEvaluateROAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); + return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value); @@ -4611,14 +4647,14 @@ private sealed class ScriptEvalMessage : Message, IMultiMessage private byte[]? asciiHash; private readonly byte[]? hexHash; - public ScriptEvalMessage(int db, CommandFlags flags, string script, RedisKey[]? keys, RedisValue[]? values) - : this(db, flags, ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL, script, null, keys, values) + public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string script, RedisKey[]? keys, RedisValue[]? values) + : this(db, flags, command, script, null, keys, values) { if (script == null) throw new ArgumentNullException(nameof(script)); } - public ScriptEvalMessage(int db, CommandFlags flags, byte[] hash, RedisKey[]? keys, RedisValue[]? values) - : this(db, flags, RedisCommand.EVALSHA, null, hash, keys, values) + public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, byte[] hash, RedisKey[]? keys, RedisValue[]? values) + : this(db, flags, command, null, hash, keys, values) { if (hash == null) throw new ArgumentNullException(nameof(hash)); if (hash.Length != ResultProcessor.ScriptLoadProcessor.Sha1HashLength) throw new ArgumentOutOfRangeException(nameof(hash), "Invalid hash length"); From 95d32d11ea70d13d39eb4ec500caa535921e1fa3 Mon Sep 17 00:00:00 2001 From: shacharPash Date: Wed, 22 Jun 2022 12:24:41 +0300 Subject: [PATCH 3/8] Naming and Wrapper --- src/StackExchange.Redis/Interfaces/IDatabase.cs | 4 ++-- src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs | 4 ++-- .../KeyspaceIsolation/DatabaseWrapper.cs | 8 ++++++++ src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs | 8 ++++++++ src/StackExchange.Redis/PublicAPI.Shipped.txt | 8 ++++---- src/StackExchange.Redis/RedisDatabase.cs | 8 ++++---- 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs index c974bb98c..5eb318d53 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabase.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -1310,7 +1310,7 @@ public interface IDatabase : IRedis, IDatabaseAsync /// , /// /// - RedisResult ScriptEvaluateRO(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); /// /// Read-only variant of the EVALSHA command that cannot execute commands that modify data, Execute a Lua script against the server using just the SHA1 hash. @@ -1321,7 +1321,7 @@ public interface IDatabase : IRedis, IDatabaseAsync /// The flags to use for this operation. /// A dynamic representation of the script's result. /// - RedisResult ScriptEvaluateRO(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + RedisResult ScriptEvaluateReadOnly(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); /// /// Add the specified member to the set stored at key. diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs index 0a2ca7ec6..30d84a10f 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -1286,7 +1286,7 @@ public interface IDatabaseAsync : IRedisAsync /// , /// /// - Task ScriptEvaluateROAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); /// /// Read-only variant of the EVALSHA command that cannot execute commands that modify data, Execute a Lua script against the server using just the SHA1 hash. @@ -1297,7 +1297,7 @@ public interface IDatabaseAsync : IRedisAsync /// The flags to use for this operation. /// A dynamic representation of the script's result. /// - Task ScriptEvaluateROAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); /// /// Add the specified member to the set stored at key. diff --git a/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs b/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs index 6fa9bfbc0..536a26ecc 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs @@ -333,6 +333,14 @@ public RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = n // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? script.Evaluate(Inner, parameters, Prefix, flags); + public RedisResult ScriptEvaluateReadOnly(byte[] hash, 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.ScriptEvaluateReadOnly(hash, ToInner(keys), values, flags); + + public RedisResult ScriptEvaluateReadOnly(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.ScriptEvaluateReadOnly(script, ToInner(keys), values, flags); + public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => Inner.SetAdd(ToInner(key), values, flags); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs b/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs index c604a21c9..1ef8a87b9 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs @@ -344,6 +344,14 @@ public Task ScriptEvaluateAsync(LoadedLuaScript script, object? par // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? script.EvaluateAsync(Inner, parameters, Prefix, flags); + public Task ScriptEvaluateReadOnlyAsync(byte[] hash, 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(hash, ToInner(keys), values, flags); + + 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); + public Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => Inner.SetAddAsync(ToInner(key), values, flags); diff --git a/src/StackExchange.Redis/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI.Shipped.txt index b2ec4bb48..686c502d9 100644 --- a/src/StackExchange.Redis/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI.Shipped.txt @@ -593,8 +593,8 @@ StackExchange.Redis.IDatabase.ScriptEvaluate(byte[]! hash, StackExchange.Redis.R StackExchange.Redis.IDatabase.ScriptEvaluate(StackExchange.Redis.LoadedLuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabase.ScriptEvaluate(StackExchange.Redis.LuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabase.ScriptEvaluate(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! -StackExchange.Redis.IDatabase.ScriptEvaluateRO(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! -StackExchange.Redis.IDatabase.ScriptEvaluateRO(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluateReadOnly(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluateReadOnly(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabase.SetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool StackExchange.Redis.IDatabase.SetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long StackExchange.Redis.IDatabase.SetCombine(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! @@ -820,8 +820,8 @@ StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(byte[]! hash, StackExchan StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(StackExchange.Redis.LoadedLuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(StackExchange.Redis.LuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.ScriptEvaluateROAsync(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.ScriptEvaluateROAsync(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateReadOnlyAsync(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateReadOnlyAsync(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.SetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.SetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.SetCombineAndStoreAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 044a6cc3d..7c3a17ce9 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -1565,7 +1565,7 @@ public Task ScriptEvaluateAsync(LoadedLuaScript script, object? par return script.EvaluateAsync(this, parameters, null, flags); } - public RedisResult ScriptEvaluateRO(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + public RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); @@ -1580,20 +1580,20 @@ public RedisResult ScriptEvaluateRO(string script, RedisKey[]? keys = null, Redi } } - public RedisResult ScriptEvaluateRO(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + public RedisResult ScriptEvaluateReadOnly(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } - public Task ScriptEvaluateROAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + public Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } - public Task ScriptEvaluateROAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + public Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); From dd1f30f3dfc9896828095a50bd6962c468b27385 Mon Sep 17 00:00:00 2001 From: shacharPash Date: Wed, 22 Jun 2022 15:15:40 +0300 Subject: [PATCH 4/8] Define EVAL_RO & EVALSHA_RO in Message.IsPrimaryOnly --- src/StackExchange.Redis/Enums/RedisCommand.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/StackExchange.Redis/Enums/RedisCommand.cs b/src/StackExchange.Redis/Enums/RedisCommand.cs index 9467f7687..03e046020 100644 --- a/src/StackExchange.Redis/Enums/RedisCommand.cs +++ b/src/StackExchange.Redis/Enums/RedisCommand.cs @@ -371,6 +371,8 @@ internal static bool IsPrimaryOnly(this RedisCommand command) case RedisCommand.ECHO: case RedisCommand.EVAL: case RedisCommand.EVALSHA: + case RedisCommand.EVAL_RO: + case RedisCommand.EVALSHA_RO: case RedisCommand.EXEC: case RedisCommand.EXISTS: case RedisCommand.GEODIST: From c840ae2817ba57df181c2f0e428013afb52c6049 Mon Sep 17 00:00:00 2001 From: Shachar Pashchur Date: Thu, 8 Dec 2022 15:43:50 +0200 Subject: [PATCH 5/8] Add TestEvalReadonly --- tests/StackExchange.Redis.Tests/Scripting.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/StackExchange.Redis.Tests/Scripting.cs b/tests/StackExchange.Redis.Tests/Scripting.cs index 0e80ff08a..85811e389 100644 --- a/tests/StackExchange.Redis.Tests/Scripting.cs +++ b/tests/StackExchange.Redis.Tests/Scripting.cs @@ -1040,6 +1040,20 @@ private static void TestNullArray(RedisResult? value) [Fact] public void RedisResultUnderstandsNullValue() => TestNullValue(RedisResult.Create(RedisValue.Null, ResultType.None)); + [Fact] + public void TestEvalReadonly() + { + using var conn = GetScriptConn(); + var db = conn.GetDatabase(); + + string script = "return KEYS[1]"; + RedisKey[] keys = new RedisKey[1] { "key1" }; + RedisValue[] values = new RedisValue[1] { "first" }; + + var result = db.ScriptEvaluateReadOnly(script, keys, values); + Assert.Equal("key1", result.ToString()); + } + private static void TestNullValue(RedisResult? value) { Assert.True(value == null || value.IsNull); From e7d20501d3ccc470cffb658103a5a8948bdd1285 Mon Sep 17 00:00:00 2001 From: Shachar Pashchur Date: Thu, 8 Dec 2022 16:20:42 +0200 Subject: [PATCH 6/8] Add TestEvalShaReadOnly + Async Tests --- tests/StackExchange.Redis.Tests/Scripting.cs | 47 ++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/StackExchange.Redis.Tests/Scripting.cs b/tests/StackExchange.Redis.Tests/Scripting.cs index 85811e389..93efcac46 100644 --- a/tests/StackExchange.Redis.Tests/Scripting.cs +++ b/tests/StackExchange.Redis.Tests/Scripting.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using StackExchange.Redis.KeyspaceIsolation; @@ -1054,6 +1055,52 @@ public void TestEvalReadonly() Assert.Equal("key1", result.ToString()); } + [Fact] + public async Task TestEvalReadonlyAsync() + { + using var conn = GetScriptConn(); + var db = conn.GetDatabase(); + + string script = "return KEYS[1]"; + RedisKey[] keys = new RedisKey[1] { "key1" }; + RedisValue[] values = new RedisValue[1] { "first" }; + + var result = await db.ScriptEvaluateReadOnlyAsync(script, keys, values); + Assert.Equal("key1", result.ToString()); + } + + [Fact] + public void TestEvalShaReadOnly() + { + using var conn = GetScriptConn(); + var db = conn.GetDatabase(); + db.StringSet("foo", "bar"); + db.ScriptEvaluate("return redis.call('get','foo')"); + // Create a SHA1 hash of the script: 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 + SHA1 sha1Hash = SHA1.Create(); + + byte[] hash = sha1Hash.ComputeHash(Encoding.UTF8.GetBytes("return redis.call('get','foo')")); + var result = db.ScriptEvaluateReadOnly(hash); + + Assert.Equal("bar", result.ToString()); + } + + [Fact] + public async Task TestEvalShaReadOnlyAsync() + { + using var conn = GetScriptConn(); + var db = conn.GetDatabase(); + db.StringSet("foo", "bar"); + db.ScriptEvaluate("return redis.call('get','foo')"); + // Create a SHA1 hash of the script: 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 + SHA1 sha1Hash = SHA1.Create(); + + byte[] hash = sha1Hash.ComputeHash(Encoding.UTF8.GetBytes("return redis.call('get','foo')")); + var result = await db.ScriptEvaluateReadOnlyAsync(hash); + + Assert.Equal("bar", result.ToString()); + } + private static void TestNullValue(RedisResult? value) { Assert.True(value == null || value.IsNull); From 81a416ee42f96d4886558fdd3634f846801a1541 Mon Sep 17 00:00:00 2001 From: Shachar Pashchur Date: Sun, 11 Dec 2022 12:47:54 +0200 Subject: [PATCH 7/8] fix ScriptEvaluateAsync --- src/StackExchange.Redis/RedisDatabase.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 06f4c5776..9df7ac742 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -1546,7 +1546,16 @@ public async Task ScriptEvaluateAsync(string script, RedisKey[]? ke { var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); - return await ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + + try + { + return await ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle).ConfigureAwait(false); + } + catch (RedisServerException) when (msg.IsScriptUnavailable) + { + // could be a NOSCRIPT; for a sync call, we can re-issue that without problem + return await ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle).ConfigureAwait(false); + } } public Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) From 09c9a0933baace678ced9b67005971f68f797264 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Wed, 4 Jan 2023 09:59:29 -0500 Subject: [PATCH 8/8] Add release notes --- docs/ReleaseNotes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index f62a99091..211bb4cbc 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -8,6 +8,7 @@ Current package versions: ## Unreleased +- Adds: Support for `EVAL_RO` and `EVALSHA_RO` via `IDatabase.ScriptEvaluateReadOnly`/`IDatabase.ScriptEvaluateReadOnlyAsync` ([#2168 by shacharPash](https://github.com/StackExchange/StackExchange.Redis/pull/2168)) - Fix [#1458](https://github.com/StackExchange/StackExchange.Redis/issues/1458): Fixes a leak condition when a connection completes on the TCP phase but not the Redis handshake ([#2238 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2238)) - Internal: ServerSnapshot: Improve API and allow filtering with custom struct enumerator ([#2337 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2337))