From d07b7de5001a9cce8f28bb1b7940468982ac718f Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 16 Jan 2023 17:03:32 +0000 Subject: [PATCH 1/6] - add ReadOnlyMemory API for Lua - add single-key API for Lua - fix incorrect use of non-RO onward call for RO Lua in key-prefix path --- .../Interfaces/IDatabase.cs | 60 ++++ .../Interfaces/IDatabaseAsync.cs | 60 ++++ .../KeyspaceIsolation/KeyPrefixed.cs | 87 +++++- .../KeyspaceIsolation/KeyPrefixedDatabase.cs | 56 +++- .../PublicAPI/PublicAPI.Unshipped.txt | 9 +- src/StackExchange.Redis/RedisDatabase.cs | 270 +++++++++++++++--- .../ScriptParameterMapper.cs | 7 +- 7 files changed, 485 insertions(+), 64 deletions(-) diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs index d0d01a201..12a76e74e 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabase.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -1264,6 +1264,36 @@ public interface IDatabase : IRedis, IDatabaseAsync /// RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + /// + /// Execute a Lua script against the server. + /// + /// The script to execute. + /// The key to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// , + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous due to type delta")] + RedisResult ScriptEvaluate(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None); + + /// + /// 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. + /// + /// , + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous due to type delta")] + RedisResult ScriptEvaluate(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None); + /// /// Execute a Lua script against the server using just the SHA1 hash. /// @@ -1316,6 +1346,36 @@ public interface IDatabase : IRedis, IDatabaseAsync /// RedisResult ScriptEvaluateReadOnly(string script, 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. + /// + /// The script to execute. + /// The key to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// , + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous due to type delta")] + RedisResult ScriptEvaluateReadOnly(string script, RedisKey key, ReadOnlyMemory values = default, 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. + /// + /// , + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous due to type delta")] + RedisResult ScriptEvaluateReadOnly(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, 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. /// diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs index 7f90cf1a5..1e4d4de91 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -1240,6 +1240,36 @@ public interface IDatabaseAsync : IRedisAsync /// Task ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + /// + /// Execute a Lua script against the server. + /// + /// The script to execute. + /// The key to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// , + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous due to type delta")] + Task ScriptEvaluateAsync(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None); + + /// + /// 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. + /// + /// , + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous due to type delta")] + Task ScriptEvaluateAsync(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None); + /// /// Execute a Lua script against the server using just the SHA1 hash. /// @@ -1303,6 +1333,36 @@ public interface IDatabaseAsync : IRedisAsync /// Task ScriptEvaluateReadOnlyAsync(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. + /// + /// The script to execute. + /// The key to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// , + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous due to type delta")] + Task ScriptEvaluateReadOnlyAsync(string script, RedisKey key, ReadOnlyMemory values = default, 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. + /// + /// , + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous due to type delta")] + Task ScriptEvaluateReadOnlyAsync(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, 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/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index 290fbed59..34aaef447 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -332,9 +333,31 @@ public Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = nul // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? Inner.ScriptEvaluateAsync(hash, ToInner(keys), values, flags); - 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); + public Task ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { // note we may end up using the memory overloads to enable better pooling etc usage + if (keys is null || keys.Length == 0) return Inner.ScriptEvaluateAsync(script, keys, values, flags); + if (keys.Length == 1) return Inner.ScriptEvaluateAsync(script, ToInner(keys[0]), values, flags); + if ((flags & CommandFlags.FireAndForget) != 0) return Inner.ScriptEvaluateAsync(script, ToInnerCopy(keys), values, flags); + return Lease_ScriptEvaluateAsync(script, keys, values, flags); + } + + public Task ScriptEvaluateAsync(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) => + Inner.ScriptEvaluateAsync(script, ToInner(key), values, flags); + + public Task ScriptEvaluateAsync(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + if (keys.Length == 0) return Inner.ScriptEvaluateAsync(script, keys, values, flags); + if (keys.Length == 1) return Inner.ScriptEvaluateAsync(script, ToInner(keys.Span[0]), values, flags); + if ((flags & CommandFlags.FireAndForget) != 0) return Inner.ScriptEvaluateAsync(script, ToInnerCopy(keys), values, flags); + return Lease_ScriptEvaluateAsync(script, keys, values, flags); + } + + private async Task Lease_ScriptEvaluateAsync(string script, ReadOnlyMemory keys, ReadOnlyMemory values, CommandFlags flags = CommandFlags.None) + { + var result = await Inner.ScriptEvaluateAsync(script, ToInnerLease(keys, out var lease), values, flags); + ArrayPool.Shared.Return(lease); // happy to only recycle on success + return result; + } 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? @@ -346,11 +369,61 @@ public Task ScriptEvaluateAsync(LoadedLuaScript script, object? par 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); + Inner.ScriptEvaluateReadOnlyAsync(hash, ToInner(keys), values, flags); + + public Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { // note we may end up using the memory overloads to enable better pooling etc usage + if (keys is null || keys.Length == 0) return Inner.ScriptEvaluateReadOnlyAsync(script, keys, values, flags); + if (keys.Length == 1) return Inner.ScriptEvaluateReadOnlyAsync(script, ToInner(keys[0]), values, flags); + if ((flags & CommandFlags.FireAndForget) != 0) return Inner.ScriptEvaluateReadOnlyAsync(script, ToInnerCopy(keys), values, flags); + return Lease_ScriptEvaluateReadOnlyAsync(script, 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 ScriptEvaluateReadOnlyAsync(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) => + Inner.ScriptEvaluateReadOnlyAsync(script, ToInner(key), values, flags); + + public Task ScriptEvaluateReadOnlyAsync(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + if (keys.Length == 0) return Inner.ScriptEvaluateReadOnlyAsync(script, keys, values, flags); + if (keys.Length == 1) return Inner.ScriptEvaluateReadOnlyAsync(script, ToInner(keys.Span[0]), values, flags); + if ((flags & CommandFlags.FireAndForget) != 0) return Inner.ScriptEvaluateReadOnlyAsync(script, ToInnerCopy(keys), values, flags); + return Lease_ScriptEvaluateReadOnlyAsync(script, keys, values, flags); + } + + private async Task Lease_ScriptEvaluateReadOnlyAsync(string script, ReadOnlyMemory keys, ReadOnlyMemory values, CommandFlags flags) + { + var result = await Inner.ScriptEvaluateReadOnlyAsync(script, ToInnerLease(keys, out var lease), values, flags); + ArrayPool.Shared.Return(lease); // happy to only recycle on success + return result; + } + + protected ReadOnlyMemory ToInnerCopy(ReadOnlyMemory keys) + { + if (keys.Length == 0) return default; + var arr = new RedisKey[keys.Length]; + ToInner(keys, arr); + return arr; + } + + protected ReadOnlyMemory ToInnerLease(ReadOnlyMemory keys, out RedisKey[] lease) + { + if (keys.Length == 0) + { + lease = Array.Empty(); + return default; + } + lease = ArrayPool.Shared.Rent(keys.Length); + return new ReadOnlyMemory(lease, 0, ToInner(keys, lease)); + } + private int ToInner(ReadOnlyMemory from, RedisKey[] arr) + { + int i = 0; + foreach (ref readonly var key in from.Span) + { + arr[i++] = ToInner(key); + } + return i; + } 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 d1c47aeab..3ecfbaa2c 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs @@ -1,6 +1,8 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Net; +using System.Threading.Tasks; namespace StackExchange.Redis.KeyspaceIsolation { @@ -321,9 +323,30 @@ public RedisResult ScriptEvaluate(byte[] hash, RedisKey[]? keys = null, RedisVal // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? Inner.ScriptEvaluate(hash, ToInner(keys), values, flags); - 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); + public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { // note we may end up using the memory overloads to enable better pooling etc usage + if (keys is null || keys.Length == 0) return Inner.ScriptEvaluate(script, keys, values, flags); + if (keys.Length == 1) return Inner.ScriptEvaluate(script, ToInner(keys[0]), values, flags); + if ((flags & CommandFlags.FireAndForget) != 0) return Inner.ScriptEvaluate(script, ToInnerCopy(keys), values, flags); + + var result = Inner.ScriptEvaluate(script, ToInnerLease(keys, out var lease), values, flags); + ArrayPool.Shared.Return(lease); // happy to only recycle on success + return result; + } + + public RedisResult ScriptEvaluate(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) => + Inner.ScriptEvaluate(script, ToInner(key), values, flags); + + public RedisResult ScriptEvaluate(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + if (keys.Length == 0) return Inner.ScriptEvaluate(script, keys, values, flags); + if (keys.Length == 1) return Inner.ScriptEvaluate(script, ToInner(keys.Span[0]), values, flags); + if ((flags & CommandFlags.FireAndForget) != 0) return Inner.ScriptEvaluate(script, ToInnerCopy(keys), values, flags); + + var result = Inner.ScriptEvaluate(script, ToInnerLease(keys, out var lease), values, flags); + ArrayPool.Shared.Return(lease); // happy to only recycle on success + return result; + } 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? @@ -337,9 +360,30 @@ public RedisResult ScriptEvaluateReadOnly(byte[] hash, RedisKey[]? keys = null, // 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 RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { // note we may end up using the memory overloads to enable better pooling etc usage + if (keys is null || keys.Length == 0) return Inner.ScriptEvaluateReadOnly(script, keys, values, flags); + if (keys.Length == 1) return Inner.ScriptEvaluateReadOnly(script, ToInner(keys[0]), values, flags); + if ((flags & CommandFlags.FireAndForget) != 0) return Inner.ScriptEvaluateReadOnly(script, ToInnerCopy(keys), values, flags); + + var result = Inner.ScriptEvaluateReadOnly(script, ToInnerLease(keys, out var lease), values, flags); + ArrayPool.Shared.Return(lease); // happy to only recycle on success + return result; + } + + public RedisResult ScriptEvaluateReadOnly(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) => + Inner.ScriptEvaluateReadOnly(script, ToInner(key), values, flags); + + public RedisResult ScriptEvaluateReadOnly(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + if (keys.Length == 0) return Inner.ScriptEvaluateReadOnly(script, keys, values, flags); + if (keys.Length == 1) return Inner.ScriptEvaluateReadOnly(script, ToInner(keys.Span[0]), values, flags); + if ((flags & CommandFlags.FireAndForget) != 0) return Inner.ScriptEvaluateReadOnly(script, ToInnerCopy(keys), values, flags); + + var result = Inner.ScriptEvaluateReadOnly(script, ToInnerLease(keys, out var lease), values, flags); + ArrayPool.Shared.Return(lease); // happy to only recycle on success + return result; + } public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => Inner.SetAdd(ToInner(key), values, flags); diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 5f282702b..255b7464d 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,8 @@ - \ No newline at end of file +StackExchange.Redis.IDatabase.ScriptEvaluate(string! script, StackExchange.Redis.RedisKey key, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluate(string! script, System.ReadOnlyMemory keys, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluateReadOnly(string! script, StackExchange.Redis.RedisKey key, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluateReadOnly(string! script, System.ReadOnlyMemory keys, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(string! script, StackExchange.Redis.RedisKey key, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(string! script, System.ReadOnlyMemory keys, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateReadOnlyAsync(string! script, StackExchange.Redis.RedisKey key, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateReadOnlyAsync(string! script, System.ReadOnlyMemory keys, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! \ No newline at end of file diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 9df7ac742..c25417b9b 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -1514,7 +1514,7 @@ public Task ExecuteAsync(string command, ICollection? args, 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); + var msg = new ScriptEvalMultiKeyMessage(Database, flags, command, script, keys, values); try { return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); @@ -1526,9 +1526,126 @@ public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisV } } + public RedisResult ScriptEvaluate(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; + var msg = new ScriptEvalSingleKeyMessage(Database, flags, command, script, key, 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(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; + var msg = new ScriptEvalMultiKeyMessage(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 ScriptEvaluateReadOnly(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; + var msg = new ScriptEvalSingleKeyMessage(Database, flags, command, script, key, 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 ScriptEvaluateReadOnly(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; + var msg = new ScriptEvalMultiKeyMessage(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 async Task ScriptEvaluateAsync(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; + var msg = new ScriptEvalSingleKeyMessage(Database, flags, command, script, key, values); + + 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 async Task ScriptEvaluateAsync(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; + var msg = new ScriptEvalMultiKeyMessage(Database, flags, command, script, keys, values); + + 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 async Task ScriptEvaluateReadOnlyAsync(string script, RedisKey key, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; + var msg = new ScriptEvalSingleKeyMessage(Database, flags, command, script, key, values); + 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 async Task ScriptEvaluateReadOnlyAsync(string script, ReadOnlyMemory keys, ReadOnlyMemory values = default, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; + var msg = new ScriptEvalMultiKeyMessage(Database, flags, command, script, keys, values); + 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 RedisResult ScriptEvaluate(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { - var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); + var msg = new ScriptEvalMultiKeyMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -1545,7 +1662,7 @@ public RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = n public async Task ScriptEvaluateAsync(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); + var msg = new ScriptEvalMultiKeyMessage(Database, flags, command, script, keys, values); try { @@ -1560,7 +1677,7 @@ public async Task ScriptEvaluateAsync(string script, RedisKey[]? ke public Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { - var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); + var msg = new ScriptEvalMultiKeyMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -1577,7 +1694,7 @@ public Task ScriptEvaluateAsync(LoadedLuaScript script, object? par 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); + var msg = new ScriptEvalMultiKeyMessage(Database, flags, command, script, keys, values); try { return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); @@ -1591,20 +1708,28 @@ public RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null 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); + var msg = new ScriptEvalMultiKeyMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } - public Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + public async 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); + var msg = new ScriptEvalMultiKeyMessage(Database, flags, command, script, keys, values); + 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 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); + var msg = new ScriptEvalMultiKeyMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -4778,51 +4903,107 @@ public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) public override int ArgCount => _args.Count; } - private sealed class ScriptEvalMessage : Message, IMultiMessage + private sealed class ScriptEvalSingleKeyMessage : ScriptEvalMessage { - private readonly RedisKey[] keys; + private readonly RedisKey key; + + protected override int KeyCount => 1; + + public ScriptEvalSingleKeyMessage(int db, CommandFlags flags, RedisCommand command, string script, RedisKey key, ReadOnlyMemory values) + : base(db, flags, command, script, values) + { + key.AssertNotNull(); + this.key = key; + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + => serverSelectionStrategy.HashSlot(key); + + protected override void WriteKeys(PhysicalConnection physical) => physical.Write(key); + } + + private sealed class ScriptEvalMultiKeyMessage : ScriptEvalMessage + { + private readonly ReadOnlyMemory keys; + + protected override int KeyCount => keys.Length; + + + public ScriptEvalMultiKeyMessage(int db, CommandFlags flags, RedisCommand command, string script, ReadOnlyMemory keys, ReadOnlyMemory values) + : base(db, flags, command, script, values) + { + AssertNotNull(keys); + this.keys = keys; + + } + + public ScriptEvalMultiKeyMessage(int db, CommandFlags flags, RedisCommand command, byte[] hash, ReadOnlyMemory keys, ReadOnlyMemory values) + : base(db, flags, command, hash, values) + { + AssertNotNull(keys); + this.keys = keys; + } + private static void AssertNotNull(ReadOnlyMemory keys) + { + foreach (ref readonly var key in keys.Span) + { + key.AssertNotNull(); + } + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + int slot = ServerSelectionStrategy.NoSlot; + foreach (ref readonly var key in keys.Span) + slot = serverSelectionStrategy.CombineSlot(slot, key); + + return slot; + } + protected override void WriteKeys(PhysicalConnection physical) + { + foreach (ref readonly var key in keys.Span) + physical.Write(key); + } + } + + private abstract class ScriptEvalMessage : Message, IMultiMessage + { + protected abstract int KeyCount { get; } + private readonly string? script; - private readonly RedisValue[] values; + + + private readonly ReadOnlyMemory values; private byte[]? asciiHash; private readonly byte[]? hexHash; - public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string script, RedisKey[]? keys, RedisValue[]? values) - : this(db, flags, command, script, null, keys, values) + public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string script, ReadOnlyMemory values) + : this(db, flags, command, script, null, values) { - if (script == null) throw new ArgumentNullException(nameof(script)); + if (script is null) throw new ArgumentNullException(nameof(script)); } - public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, byte[] hash, RedisKey[]? keys, RedisValue[]? values) - : this(db, flags, command, null, hash, keys, values) + public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, byte[] hash, ReadOnlyMemory values) + : this(db, flags, command, null, hash, values) { - if (hash == null) throw new ArgumentNullException(nameof(hash)); + if (hash is null) throw new ArgumentNullException(nameof(hash)); if (hash.Length != ResultProcessor.ScriptLoadProcessor.Sha1HashLength) throw new ArgumentOutOfRangeException(nameof(hash), "Invalid hash length"); } - private ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string? script, byte[]? hexHash, RedisKey[]? keys, RedisValue[]? values) + private ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string? script, byte[]? hexHash, ReadOnlyMemory values) : base(db, flags, command) { this.script = script; this.hexHash = hexHash; - if (keys == null) keys = Array.Empty(); - if (values == null) values = Array.Empty(); - for (int i = 0; i < keys.Length; i++) - keys[i].AssertNotNull(); - this.keys = keys; - for (int i = 0; i < values.Length; i++) - values[i].AssertNotNull(); + //foreach (ref readonly var key in keys.Span) + // key.AssertNotNull(); + //multiKeys = keys; + foreach (ref readonly var value in values.Span) + value.AssertNotNull(); this.values = values; } - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - int slot = ServerSelectionStrategy.NoSlot; - for (int i = 0; i < keys.Length; i++) - slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); - return slot; - } - public IEnumerable GetMessages(PhysicalConnection connection) { PhysicalBridge? bridge; @@ -4844,30 +5025,31 @@ public IEnumerable GetMessages(PhysicalConnection connection) yield return this; } + protected abstract void WriteKeys(PhysicalConnection physical); protected override void WriteImpl(PhysicalConnection physical) { + int keyCount = KeyCount; if (hexHash != null) { - physical.WriteHeader(RedisCommand.EVALSHA, 2 + keys.Length + values.Length); + physical.WriteHeader(RedisCommand.EVALSHA, 2 + keyCount + values.Length); physical.WriteSha1AsHex(hexHash); } else if (asciiHash != null) { - physical.WriteHeader(RedisCommand.EVALSHA, 2 + keys.Length + values.Length); + physical.WriteHeader(RedisCommand.EVALSHA, 2 + keyCount + values.Length); physical.WriteBulkString((RedisValue)asciiHash); } else { - physical.WriteHeader(RedisCommand.EVAL, 2 + keys.Length + values.Length); + physical.WriteHeader(RedisCommand.EVAL, 2 + keyCount + values.Length); physical.WriteBulkString((RedisValue)script); } - physical.WriteBulkString(keys.Length); - for (int i = 0; i < keys.Length; i++) - physical.Write(keys[i]); - for (int i = 0; i < values.Length; i++) - physical.WriteBulkString(values[i]); + physical.WriteBulkString(keyCount); + WriteKeys(physical); + foreach (ref readonly var value in values.Span) + physical.WriteBulkString(value); } - public override int ArgCount => 2 + keys.Length + values.Length; + public override int ArgCount => 2 + KeyCount + values.Length; } private sealed class SetScanResultProcessor : ScanResultProcessor diff --git a/src/StackExchange.Redis/ScriptParameterMapper.cs b/src/StackExchange.Redis/ScriptParameterMapper.cs index 2c0e76314..4bb2e2c6d 100644 --- a/src/StackExchange.Redis/ScriptParameterMapper.cs +++ b/src/StackExchange.Redis/ScriptParameterMapper.cs @@ -224,12 +224,7 @@ public static bool IsValidParameterHash(Type t, LuaScript script, out string? mi for (var i = 0; i < script.Arguments.Length; i++) { var argName = script.Arguments[i]; - var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo); - if (member is null) - { - throw new ArgumentException($"There was no member found for {argName}"); - } - + var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo) ?? throw new ArgumentException($"There was no member found for {argName}"); var memberType = member is FieldInfo memberFieldInfo ? memberFieldInfo.FieldType : ((PropertyInfo)member).PropertyType; if (memberType == typeof(RedisKey)) From b8445b7af871e670cff28e7826b2d5026f060cea Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 17 Jan 2023 10:41:06 +0000 Subject: [PATCH 2/6] add a fews 'in' uses --- src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs | 4 ++-- src/StackExchange.Redis/RedisDatabase.cs | 2 +- src/StackExchange.Redis/RedisKey.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index 34aaef447..b0cdb6cd4 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -807,10 +807,10 @@ public void Wait(Task task) => public void WaitAll(params Task[] tasks) => Inner.WaitAll(tasks); - protected internal RedisKey ToInner(RedisKey outer) => + protected internal RedisKey ToInner(in RedisKey outer) => RedisKey.WithPrefix(Prefix, outer); - protected RedisKey ToInnerOrDefault(RedisKey outer) => + protected RedisKey ToInnerOrDefault(in RedisKey outer) => (outer == default(RedisKey)) ? outer : ToInner(outer); [return: NotNullIfNotNull("args")] diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index c25417b9b..53509121b 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -4909,7 +4909,7 @@ private sealed class ScriptEvalSingleKeyMessage : ScriptEvalMessage protected override int KeyCount => 1; - public ScriptEvalSingleKeyMessage(int db, CommandFlags flags, RedisCommand command, string script, RedisKey key, ReadOnlyMemory values) + public ScriptEvalSingleKeyMessage(int db, CommandFlags flags, RedisCommand command, string script, in RedisKey key, ReadOnlyMemory values) : base(db, flags, command, script, values) { key.AssertNotNull(); diff --git a/src/StackExchange.Redis/RedisKey.cs b/src/StackExchange.Redis/RedisKey.cs index 28378d116..165b51e46 100644 --- a/src/StackExchange.Redis/RedisKey.cs +++ b/src/StackExchange.Redis/RedisKey.cs @@ -314,7 +314,7 @@ public static implicit operator RedisKey(byte[]? key) public static RedisKey operator +(RedisKey x, RedisKey y) => new RedisKey(ConcatenateBytes(x.KeyPrefix, x.KeyValue, y.KeyPrefix), y.KeyValue); - internal static RedisKey WithPrefix(byte[]? prefix, RedisKey value) + internal static RedisKey WithPrefix(byte[]? prefix, in RedisKey value) { if (prefix == null || prefix.Length == 0) return value; if (value.KeyPrefix == null) return new RedisKey(prefix, value.KeyValue); From ff52ab9dc7d84951bd932b9064b8274a6048fb1b Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 17 Jan 2023 12:33:55 +0000 Subject: [PATCH 3/6] fix build break --- src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index b0cdb6cd4..741d9e481 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -920,6 +920,8 @@ protected RedisChannel ToInner(RedisChannel outer) => private Func? mapFunction; protected Func GetMapFunction() => // create as a delegate when first required, then re-use - mapFunction ??= new Func(ToInner); + mapFunction ??= CreateMapFunction(); // avoid inlining this because of capture scopes etc + + private Func CreateMapFunction() => key => ToInner(key); } } From e61a48e763d265652a9119a9fa0fda2eaae8fe95 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 17 Jan 2023 12:34:30 +0000 Subject: [PATCH 4/6] entirely speculative "union" key/value API for discussion --- .../PublicAPI/PublicAPI.Unshipped.txt | 22 +++- src/StackExchange.Redis/RedisKeyOrValue.cs | 115 ++++++++++++++++++ src/StackExchange.Redis/RedisValue.cs | 4 +- 3 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 src/StackExchange.Redis/RedisKeyOrValue.cs diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 255b7464d..a5c5a7d36 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1,8 +1,26 @@ -StackExchange.Redis.IDatabase.ScriptEvaluate(string! script, StackExchange.Redis.RedisKey key, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +override StackExchange.Redis.RedisKeyOrValue.Equals(object? obj) -> bool +override StackExchange.Redis.RedisKeyOrValue.GetHashCode() -> int +override StackExchange.Redis.RedisKeyOrValue.ToString() -> string! +StackExchange.Redis.IDatabase.ScriptEvaluate(string! script, StackExchange.Redis.RedisKey key, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabase.ScriptEvaluate(string! script, System.ReadOnlyMemory keys, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabase.ScriptEvaluateReadOnly(string! script, StackExchange.Redis.RedisKey key, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabase.ScriptEvaluateReadOnly(string! script, System.ReadOnlyMemory keys, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(string! script, StackExchange.Redis.RedisKey key, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(string! script, System.ReadOnlyMemory keys, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.ScriptEvaluateReadOnlyAsync(string! script, StackExchange.Redis.RedisKey key, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! -StackExchange.Redis.IDatabaseAsync.ScriptEvaluateReadOnlyAsync(string! script, System.ReadOnlyMemory keys, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! \ No newline at end of file +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateReadOnlyAsync(string! script, System.ReadOnlyMemory keys, System.ReadOnlyMemory values = default(System.ReadOnlyMemory), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.RedisKeyOrValue +StackExchange.Redis.RedisKeyOrValue.AsKey() -> StackExchange.Redis.RedisKey +StackExchange.Redis.RedisKeyOrValue.AsValue() -> StackExchange.Redis.RedisValue +StackExchange.Redis.RedisKeyOrValue.Equals(StackExchange.Redis.RedisKey other) -> bool +StackExchange.Redis.RedisKeyOrValue.Equals(StackExchange.Redis.RedisKeyOrValue other) -> bool +StackExchange.Redis.RedisKeyOrValue.Equals(StackExchange.Redis.RedisValue other) -> bool +StackExchange.Redis.RedisKeyOrValue.IsKey.get -> bool +StackExchange.Redis.RedisKeyOrValue.IsValue.get -> bool +StackExchange.Redis.RedisKeyOrValue.RedisKeyOrValue() -> void +static StackExchange.Redis.RedisKeyOrValue.explicit operator StackExchange.Redis.RedisKey(StackExchange.Redis.RedisKeyOrValue value) -> StackExchange.Redis.RedisKey +static StackExchange.Redis.RedisKeyOrValue.explicit operator StackExchange.Redis.RedisValue(StackExchange.Redis.RedisKeyOrValue value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisKeyOrValue.implicit operator StackExchange.Redis.RedisKeyOrValue(StackExchange.Redis.RedisKey key) -> StackExchange.Redis.RedisKeyOrValue +static StackExchange.Redis.RedisKeyOrValue.implicit operator StackExchange.Redis.RedisKeyOrValue(StackExchange.Redis.RedisValue value) -> StackExchange.Redis.RedisKeyOrValue +static StackExchange.Redis.RedisKeyOrValue.Key(StackExchange.Redis.RedisKey key) -> StackExchange.Redis.RedisKeyOrValue +static StackExchange.Redis.RedisKeyOrValue.Value(StackExchange.Redis.RedisValue value) -> StackExchange.Redis.RedisKeyOrValue \ No newline at end of file diff --git a/src/StackExchange.Redis/RedisKeyOrValue.cs b/src/StackExchange.Redis/RedisKeyOrValue.cs new file mode 100644 index 000000000..25bcbde9d --- /dev/null +++ b/src/StackExchange.Redis/RedisKeyOrValue.cs @@ -0,0 +1,115 @@ +using System; +using System.Runtime.InteropServices; + +namespace StackExchange.Redis; + +/// +/// Represents a placeholder that could represent a redis key or a redis value +/// +public readonly struct RedisKeyOrValue : IEquatable, IEquatable, IEquatable +{ + private enum Mode + { + Empty, + Key, + Value, + } + private readonly Mode _mode; + private readonly object? _objectOrSentinel; + private readonly ReadOnlyMemory _memory; + private readonly long _overlappedBits64; + + private RedisKeyOrValue(Mode mode, long overlappedBits64, ReadOnlyMemory memory, object? objectOrSentinel) : this() + { + _mode = mode; + _overlappedBits64 = overlappedBits64; + _memory = memory; + _objectOrSentinel = objectOrSentinel; + } + + /// + public override int GetHashCode() + => _mode switch + { + Mode.Key => AsKey().GetHashCode(), + Mode.Value => AsValue().GetHashCode(), + _ => 0, + }; + + /// + public override bool Equals(object? obj) => obj switch + { + RedisKeyOrValue other => Equals(other), + RedisKey key => _mode == Mode.Key && AsKey().Equals(key), + RedisValue value => _mode == Mode.Value && AsValue().Equals(value), + _ => false, + }; + + /// + public override string ToString() + => _mode switch + { + Mode.Key => AsKey().ToString(), + Mode.Value => AsValue().ToString(), + _ => _mode.ToString(), + }; + + /// Create a new instance representing a key + public static RedisKeyOrValue Key(RedisKey key) + => new RedisKeyOrValue(Mode.Key, 0, key.KeyPrefix, key.KeyValue); + + /// Create a new instance representing a value + public static RedisKeyOrValue Value(RedisValue value) + => new RedisKeyOrValue(Mode.Value, value.DirectOverlappedBits64, value.DirectMemory, value.DirectObject); + + /// Create a new instance representing a key + public static implicit operator RedisKeyOrValue(RedisKey key) => Key(key); + /// Create a new instance representing a value + public static implicit operator RedisKeyOrValue(RedisValue value) => Value(value); + /// Obtains the underlying payload as a key + public static explicit operator RedisKey(RedisKeyOrValue value) => value.AsKey(); + /// Obtains the underlying payload as a value + public static explicit operator RedisValue(RedisKeyOrValue value) => value.AsValue(); + + /// Indicates whether this instance represents a key + public bool IsKey => _mode == Mode.Key; + /// Indicates whether this instance represents a value + public bool IsValue => _mode == Mode.Value; + + /// Obtains the underlying payload as a key + public RedisKey AsKey() + { + AssertMode(Mode.Key); + byte[]? keyPrefix = null; + if (MemoryMarshal.TryGetArray(_memory, out var segment) && segment.Array is not null && segment.Offset == 0 && segment.Count == segment.Array.Length) + { + keyPrefix = segment.Array; + } + return new RedisKey(keyPrefix, _objectOrSentinel); + } + + /// Obtains the underlying payload as a value + public RedisValue AsValue() + { + AssertMode(Mode.Value); + return new RedisValue(_overlappedBits64, _memory, _objectOrSentinel); + } + + private void AssertMode(Mode mode) + { + if (mode != _mode) Throw(_mode); + static void Throw(Mode mode) => throw new InvalidOperationException($"Operation not valid on {mode} value"); + } + + /// + public bool Equals(RedisKeyOrValue other) => _mode switch + { + Mode.Key => other._mode == Mode.Key && AsKey().Equals(other.AsKey()), + Mode.Value => other._mode == Mode.Value && AsValue().Equals(other.AsValue()), + _ => other._mode == _mode, + }; + /// + public bool Equals(RedisKey other) => _mode == Mode.Key && AsKey().Equals(other); + /// + public bool Equals(RedisValue other) => _mode == Mode.Value && AsValue().Equals(other); +} diff --git a/src/StackExchange.Redis/RedisValue.cs b/src/StackExchange.Redis/RedisValue.cs index 45f23474b..261870f95 100644 --- a/src/StackExchange.Redis/RedisValue.cs +++ b/src/StackExchange.Redis/RedisValue.cs @@ -21,7 +21,7 @@ namespace StackExchange.Redis private readonly ReadOnlyMemory _memory; private readonly long _overlappedBits64; - private RedisValue(long overlappedValue64, ReadOnlyMemory memory, object? objectOrSentinel) + internal RedisValue(long overlappedValue64, ReadOnlyMemory memory, object? objectOrSentinel) { _overlappedBits64 = overlappedValue64; _memory = memory; @@ -45,6 +45,8 @@ public RedisValue(string value) : this(0, default, value) { } internal object? DirectObject => _objectOrSentinel; [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1085:Use auto-implemented property.", Justification = "Intentional field ref")] internal long DirectOverlappedBits64 => _overlappedBits64; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1085:Use auto-implemented property.", Justification = "Intentional field ref")] + internal ReadOnlyMemory DirectMemory => _memory; private static readonly object Sentinel_SignedInteger = new(); private static readonly object Sentinel_UnsignedInteger = new(); From fa84c00d456fc127d708575064ce67fd8ad5ecf1 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 17 Jan 2023 13:47:00 +0000 Subject: [PATCH 5/6] tidy --- .../KeyspaceIsolation/KeyPrefixed.cs | 3 +- src/StackExchange.Redis/RedisKeyOrValue.cs | 48 ++++++++++++------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index 741d9e481..79f336e92 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -922,6 +922,7 @@ protected Func GetMapFunction() => // create as a delegate when first required, then re-use mapFunction ??= CreateMapFunction(); // avoid inlining this because of capture scopes etc - private Func CreateMapFunction() => key => ToInner(key); + private Func CreateMapFunction() => ToInnerNoRef; + private RedisKey ToInnerNoRef(RedisKey value) => ToInner(value); } } diff --git a/src/StackExchange.Redis/RedisKeyOrValue.cs b/src/StackExchange.Redis/RedisKeyOrValue.cs index 25bcbde9d..3c1ce098f 100644 --- a/src/StackExchange.Redis/RedisKeyOrValue.cs +++ b/src/StackExchange.Redis/RedisKeyOrValue.cs @@ -14,17 +14,26 @@ private enum Mode Key, Value, } + private readonly Mode _mode; private readonly object? _objectOrSentinel; private readonly ReadOnlyMemory _memory; private readonly long _overlappedBits64; - private RedisKeyOrValue(Mode mode, long overlappedBits64, ReadOnlyMemory memory, object? objectOrSentinel) : this() + private RedisKeyOrValue(in RedisKey key) + { + _mode = Mode.Key; + _overlappedBits64 = 0; + _memory = key.KeyPrefix; + _objectOrSentinel = key.KeyValue; + } + + private RedisKeyOrValue(in RedisValue value) { - _mode = mode; - _overlappedBits64 = overlappedBits64; - _memory = memory; - _objectOrSentinel = objectOrSentinel; + _mode = Mode.Value; + _overlappedBits64 = value.DirectOverlappedBits64; + _memory = value.DirectMemory; + _objectOrSentinel = value.DirectObject; } /// @@ -46,33 +55,34 @@ public override int GetHashCode() }; /// - public override string ToString() - => _mode switch - { - Mode.Key => AsKey().ToString(), - Mode.Value => AsValue().ToString(), - _ => _mode.ToString(), - }; + public override string ToString() => _mode switch + { + Mode.Key => AsKey().ToString(), + Mode.Value => AsValue().ToString(), + _ => _mode.ToString(), + }; /// Create a new instance representing a key - public static RedisKeyOrValue Key(RedisKey key) - => new RedisKeyOrValue(Mode.Key, 0, key.KeyPrefix, key.KeyValue); + public static RedisKeyOrValue Key(RedisKey key) => new RedisKeyOrValue(in key); /// Create a new instance representing a value - public static RedisKeyOrValue Value(RedisValue value) - => new RedisKeyOrValue(Mode.Value, value.DirectOverlappedBits64, value.DirectMemory, value.DirectObject); + public static RedisKeyOrValue Value(RedisValue value) => new RedisKeyOrValue(in value); /// Create a new instance representing a key - public static implicit operator RedisKeyOrValue(RedisKey key) => Key(key); + public static implicit operator RedisKeyOrValue(RedisKey key) => new RedisKeyOrValue(in key); + /// Create a new instance representing a value - public static implicit operator RedisKeyOrValue(RedisValue value) => Value(value); + public static implicit operator RedisKeyOrValue(RedisValue value) => new RedisKeyOrValue(in value); + /// Obtains the underlying payload as a key public static explicit operator RedisKey(RedisKeyOrValue value) => value.AsKey(); + /// Obtains the underlying payload as a value public static explicit operator RedisValue(RedisKeyOrValue value) => value.AsValue(); /// Indicates whether this instance represents a key public bool IsKey => _mode == Mode.Key; + /// Indicates whether this instance represents a value public bool IsValue => _mode == Mode.Value; @@ -108,8 +118,10 @@ private void AssertMode(Mode mode) Mode.Value => other._mode == Mode.Value && AsValue().Equals(other.AsValue()), _ => other._mode == _mode, }; + /// public bool Equals(RedisKey other) => _mode == Mode.Key && AsKey().Equals(other); + /// public bool Equals(RedisValue other) => _mode == Mode.Value && AsValue().Equals(other); } From 50e77eca319973a8605697c4b3d22eabaae220db Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 17 Jan 2023 15:37:10 +0000 Subject: [PATCH 6/6] add OverloadCompatTests examples --- .../PublicAPI/PublicAPI.Unshipped.txt | 1 + src/StackExchange.Redis/RedisKey.cs | 3 + .../OverloadCompatTests.cs | 114 ++++++++++++++++++ 3 files changed, 118 insertions(+) diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index a5c5a7d36..db3ce5e7d 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -18,6 +18,7 @@ StackExchange.Redis.RedisKeyOrValue.Equals(StackExchange.Redis.RedisValue other) StackExchange.Redis.RedisKeyOrValue.IsKey.get -> bool StackExchange.Redis.RedisKeyOrValue.IsValue.get -> bool StackExchange.Redis.RedisKeyOrValue.RedisKeyOrValue() -> void +static StackExchange.Redis.RedisKey.EmptyKeys.get -> System.ReadOnlyMemory static StackExchange.Redis.RedisKeyOrValue.explicit operator StackExchange.Redis.RedisKey(StackExchange.Redis.RedisKeyOrValue value) -> StackExchange.Redis.RedisKey static StackExchange.Redis.RedisKeyOrValue.explicit operator StackExchange.Redis.RedisValue(StackExchange.Redis.RedisKeyOrValue value) -> StackExchange.Redis.RedisValue static StackExchange.Redis.RedisKeyOrValue.implicit operator StackExchange.Redis.RedisKeyOrValue(StackExchange.Redis.RedisKey key) -> StackExchange.Redis.RedisKeyOrValue diff --git a/src/StackExchange.Redis/RedisKey.cs b/src/StackExchange.Redis/RedisKey.cs index 165b51e46..82521ef78 100644 --- a/src/StackExchange.Redis/RedisKey.cs +++ b/src/StackExchange.Redis/RedisKey.cs @@ -42,6 +42,9 @@ internal bool IsEmpty internal byte[]? KeyPrefix { get; } internal object? KeyValue { get; } + /// Gets an empty chunk of keys as a "memory" + public static ReadOnlyMemory EmptyKeys => default; + /// /// Indicate whether two keys are not equal. /// diff --git a/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs b/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs index 91abbf5e9..d3f3c6e7d 100644 --- a/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs +++ b/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs @@ -253,4 +253,118 @@ public async Task StringSet() await db.StringSetAsync(key, val, null, When.NotExists); await db.StringSetAsync(key, val, null, When.NotExists, flags); } + + [Fact] + public async Task ScriptEvaluate() + { + using var conn = Create(); + var db = conn.GetDatabase(); + var key = Me(); + + RedisKey[] keyArr = Array.Empty(); + ReadOnlyMemory keyRom = keyArr; + + RedisValue[] valueArr= Array.Empty(); + ReadOnlyMemory valueRom = valueArr; + + const string script = "return 0"; + + // sync + + db.ScriptEvaluate(script); + db.ScriptEvaluate(script, keyArr); + db.ScriptEvaluate(script, keyRom); + // db.ScriptEvaluate(script, default); // BOOM + // db.ScriptEvaluate(script, null); // BOOM + db.ScriptEvaluate(script, (RedisKey)default); + db.ScriptEvaluate(script, (RedisKey[]?)null); + db.ScriptEvaluate(script, (ReadOnlyMemory)null); + db.ScriptEvaluate(script, (RedisKey[]?)default); + db.ScriptEvaluate(script, (ReadOnlyMemory)default); + db.ScriptEvaluate(script, (RedisKey)default); + db.ScriptEvaluate(script, default(RedisKey[]?)); + db.ScriptEvaluate(script, default(RedisKey)); + db.ScriptEvaluate(script, default(ReadOnlyMemory)); + + db.ScriptEvaluate(script, values: valueArr); + db.ScriptEvaluate(script, keyArr, values: valueArr); + db.ScriptEvaluate(script, keyRom, values: valueArr); + db.ScriptEvaluate(script, null, values: valueArr); + db.ScriptEvaluate(script, default, values: valueArr); + + db.ScriptEvaluate(script, keyArr, values: valueRom); + db.ScriptEvaluate(script, keyRom, values: valueRom); + // db.ScriptEvaluate(script, default, values: valueRom); // BOOM + // db.ScriptEvaluate(script, null, values: valueRom); // BOOM + db.ScriptEvaluate(script, (RedisKey)default, values: valueRom); + db.ScriptEvaluate(script, (RedisKey[]?)null, values: valueRom); + db.ScriptEvaluate(script, (ReadOnlyMemory)null, values: valueRom); + db.ScriptEvaluate(script, (RedisKey[]?)default, values: valueRom); + db.ScriptEvaluate(script, (ReadOnlyMemory)default, values: valueRom); + db.ScriptEvaluate(script, (RedisKey)default, values: valueRom); + db.ScriptEvaluate(script, default(RedisKey[]?), values: valueRom); + db.ScriptEvaluate(script, default(RedisKey), values: valueRom); + db.ScriptEvaluate(script, default(ReadOnlyMemory), values: valueRom); + + db.ScriptEvaluate(script, values: null); + db.ScriptEvaluate(script, keyArr, values: null); + db.ScriptEvaluate(script, keyRom, values: null); + db.ScriptEvaluate(script, null, values: null); + db.ScriptEvaluate(script, default, values: null); + + db.ScriptEvaluate(script, values: default); + db.ScriptEvaluate(script, keyArr, values: default); + db.ScriptEvaluate(script, keyRom, values: default); + db.ScriptEvaluate(script, null, values: default); + db.ScriptEvaluate(script, default, values: default); + + // async + + await db.ScriptEvaluateAsync(script); + await db.ScriptEvaluateAsync(script, keyArr); + await db.ScriptEvaluateAsync(script, keyRom); + // await db.ScriptEvaluateAsync(script, default); // BOOM + // await db.ScriptEvaluateAsync(script, null); // BOOM + await db.ScriptEvaluateAsync(script, (RedisKey)default); + await db.ScriptEvaluateAsync(script, (RedisKey[]?)null); + await db.ScriptEvaluateAsync(script, (ReadOnlyMemory)null); + await db.ScriptEvaluateAsync(script, (RedisKey[]?)default); + await db.ScriptEvaluateAsync(script, (ReadOnlyMemory)default); + await db.ScriptEvaluateAsync(script, (RedisKey)default); + await db.ScriptEvaluateAsync(script, default(RedisKey[]?)); + await db.ScriptEvaluateAsync(script, default(RedisKey)); + await db.ScriptEvaluateAsync(script, default(ReadOnlyMemory)); + + await db.ScriptEvaluateAsync(script, values: valueArr); + await db.ScriptEvaluateAsync(script, keyArr, values: valueArr); + await db.ScriptEvaluateAsync(script, keyRom, values: valueArr); + await db.ScriptEvaluateAsync(script, null, values: valueArr); + await db.ScriptEvaluateAsync(script, default, values: valueArr); + + await db.ScriptEvaluateAsync(script, keyArr, values: valueRom); + await db.ScriptEvaluateAsync(script, keyRom, values: valueRom); + // await db.ScriptEvaluateAsync(script, default, values: valueRom); // BOOM + // await db.ScriptEvaluateAsync(script, null, values: valueRom); // BOOM + await db.ScriptEvaluateAsync(script, (RedisKey)default, values: valueRom); + await db.ScriptEvaluateAsync(script, (RedisKey[]?)null, values: valueRom); + await db.ScriptEvaluateAsync(script, (ReadOnlyMemory)null, values: valueRom); + await db.ScriptEvaluateAsync(script, (RedisKey[]?)default, values: valueRom); + await db.ScriptEvaluateAsync(script, (ReadOnlyMemory)default, values: valueRom); + await db.ScriptEvaluateAsync(script, (RedisKey)default, values: valueRom); + await db.ScriptEvaluateAsync(script, default(RedisKey[]?), values: valueRom); + await db.ScriptEvaluateAsync(script, default(RedisKey), values: valueRom); + await db.ScriptEvaluateAsync(script, default(ReadOnlyMemory), values: valueRom); + + await db.ScriptEvaluateAsync(script, values: null); + await db.ScriptEvaluateAsync(script, keyArr, values: null); + await db.ScriptEvaluateAsync(script, keyRom, values: null); + await db.ScriptEvaluateAsync(script, null, values: null); + await db.ScriptEvaluateAsync(script, default, values: null); + + await db.ScriptEvaluateAsync(script, values: default); + await db.ScriptEvaluateAsync(script, keyArr, values: default); + await db.ScriptEvaluateAsync(script, keyRom, values: default); + await db.ScriptEvaluateAsync(script, null, values: default); + await db.ScriptEvaluateAsync(script, default, values: default); + } }