Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,30 @@ public partial interface IDatabase : IRedis, IDatabaseAsync
/// <remarks><seealso href="https://redis.io/commands/publish"/></remarks>
long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Execute an arbitrary command against the server; this is primarily intended for executing modules,
/// but may also be used to provide access to new features that lack a direct API.
/// </summary>
/// <param name="command">The command to run.</param>
/// <param name="args">The arguments to pass for the command.</param>
/// <param name="argsDisposer">The arguments data disposer.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the command's result.</returns>
/// <remarks>This API should be considered an advanced feature; inappropriate use can be harmful.</remarks>
RedisResult Exec(string command, ReadOnlyMemory<RedisKeyOrValue> args = default, IRequestDisposer? argsDisposer = null, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Execute an arbitrary command against the server; this is primarily intended for executing modules,
/// but may also be used to provide access to new features that lack a direct API.
/// </summary>
/// <param name="command">The command to run.</param>
/// <param name="args">The arguments to pass for the command.</param>
/// <param name="argsDisposer">The arguments data disposer.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the command's result.</returns>
/// <remarks>This API should be considered an advanced feature; inappropriate use can be harmful.</remarks>
Lease<byte>? ExecLease(string command, ReadOnlyMemory<RedisKeyOrValue> args = default, IRequestDisposer? argsDisposer = null, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Execute an arbitrary command against the server; this is primarily intended for executing modules,
/// but may also be used to provide access to new features that lack a direct API.
Expand All @@ -1627,6 +1651,34 @@ public partial interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>This API should be considered an advanced feature; inappropriate use can be harmful.</remarks>
RedisResult Execute(string command, ICollection<object> args, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Execute a Lua script against the server.
/// </summary>
/// <param name="script">The script to execute.</param>
/// <param name="args">The args to execute against.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the script's result.</returns>
/// <remarks>
/// See
/// <seealso href="https://redis.io/commands/eval"/>,
/// <seealso href="https://redis.io/commands/evalsha"/>.
/// </remarks>
RedisResult ScriptEval(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Execute a Lua script against the server.
/// </summary>
/// <param name="script">The script to execute.</param>
/// <param name="args">The args to execute against.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the script's scalar result.</returns>
/// <remarks>
/// See
/// <seealso href="https://redis.io/commands/eval"/>,
/// <seealso href="https://redis.io/commands/evalsha"/>.
/// </remarks>
Lease<byte>? ScriptEvalLease(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Execute a Lua script against the server.
/// </summary>
Expand Down Expand Up @@ -1680,6 +1732,34 @@ public partial interface IDatabase : IRedis, IDatabaseAsync
/// <remarks><seealso href="https://redis.io/commands/eval"/></remarks>
RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a Lua script against the server.
/// </summary>
/// <param name="script">The script to execute.</param>
/// <param name="args">The args to execute against.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the script's result.</returns>
/// <remarks>
/// See
/// <seealso href="https://redis.io/commands/eval_ro"/>,
/// <seealso href="https://redis.io/commands/evalsha_ro"/>.
/// </remarks>
RedisResult ScriptEvalReadOnly(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a Lua script against the server.
/// </summary>
/// <param name="script">The script to execute.</param>
/// <param name="args">The args to execute against.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the script's result.</returns>
/// <remarks>
/// See
/// <seealso href="https://redis.io/commands/eval_ro"/>,
/// <seealso href="https://redis.io/commands/evalsha_ro"/>.
/// </remarks>
Lease<byte>? ScriptEvalReadOnlyLease(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a Lua script against the server.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -396,12 +396,24 @@ public partial interface IDatabaseAsync : IRedisAsync
/// <inheritdoc cref="IDatabase.Publish(RedisChannel, RedisValue, CommandFlags)"/>
Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.Exec(string, ReadOnlyMemory{RedisKeyOrValue}, IRequestDisposer, CommandFlags)"/>
Task<RedisResult> ExecAsync(string command, ReadOnlyMemory<RedisKeyOrValue> args = default, IRequestDisposer? argsDisposer = null, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.ExecLease(string, ReadOnlyMemory{RedisKeyOrValue}, IRequestDisposer, CommandFlags)"/>
Task<Lease<byte>?> ExecLeaseAsync(string command, ReadOnlyMemory<RedisKeyOrValue> args = default, IRequestDisposer? argsDisposer = null, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.Execute(string, object[])"/>
Task<RedisResult> ExecuteAsync(string command, params object[] args);

/// <inheritdoc cref="IDatabase.Execute(string, ICollection{object}, CommandFlags)"/>
Task<RedisResult> ExecuteAsync(string command, ICollection<object>? args, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.ScriptEval(string, ReadOnlyMemory{RedisKeyOrValue}, CommandFlags)"/>
Task<RedisResult> ScriptEvalAsync(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.ScriptEvalLease(string, ReadOnlyMemory{RedisKeyOrValue}, CommandFlags)"/>
Task<Lease<byte>?> ScriptEvalLeaseAsync(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.ScriptEvaluate(string, RedisKey[], RedisValue[], CommandFlags)"/>
Task<RedisResult> ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None);

Expand All @@ -415,6 +427,12 @@ public partial interface IDatabaseAsync : IRedisAsync
/// <inheritdoc cref="IDatabase.ScriptEvaluate(LoadedLuaScript, object?, CommandFlags)"/>
Task<RedisResult> ScriptEvaluateAsync(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.ScriptEvalReadOnly(string, ReadOnlyMemory{RedisKeyOrValue}, CommandFlags)"/>
Task<RedisResult> ScriptEvalReadOnlyAsync(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.ScriptEvalReadOnlyLease(string, ReadOnlyMemory{RedisKeyOrValue}, CommandFlags)"/>
Task<Lease<byte>?> ScriptEvalReadOnlyLeaseAsync(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="IDatabase.ScriptEvaluateReadOnly(string, RedisKey[], RedisValue[], CommandFlags)"/>
Task<RedisResult> ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None);

Expand Down
15 changes: 15 additions & 0 deletions src/StackExchange.Redis/Interfaces/IRequestDisposer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace StackExchange.Redis;

/// <summary>
/// Disposing the request data.
/// </summary>
public interface IRequestDisposer
{
/// <summary>
/// Disposing the request data.
/// </summary>
/// <param name="args">lua script request.</param>
void Dispose(ReadOnlyMemory<RedisKeyOrValue> args);
}
142 changes: 142 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
Expand Down Expand Up @@ -409,6 +410,30 @@ public Task<LCSMatchResult> StringLongestCommonSubsequenceWithMatchesAsync(Redis
public Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) =>
Inner.PublishAsync(ToInner(channel), message, flags);

public Task<RedisResult> ExecAsync(string command, ReadOnlyMemory<RedisKeyOrValue> args = default, IRequestDisposer? argsDisposer = null, CommandFlags flags = CommandFlags.None)
{
// You cannot return an array twice or an array that is not from the pool.
argsDisposer = null;

if ((flags & CommandFlags.FireAndForget) != 0)
return Inner.ExecAsync(command, ToInnerCopy(args), argsDisposer, flags);

var result = Inner.ExecAsync(command, ToInnerLease(args, out var lease), argsDisposer, flags);
return lease != null ? ReturnAfterResult(result, lease) : result;
}

public Task<Lease<byte>?> ExecLeaseAsync(string command, ReadOnlyMemory<RedisKeyOrValue> args = default, IRequestDisposer? argsDisposer = null, CommandFlags flags = CommandFlags.None)
{
// You cannot return an array twice or an array that is not from the pool.
argsDisposer = null;

if ((flags & CommandFlags.FireAndForget) != 0)
return Inner.ExecLeaseAsync(command, ToInnerCopy(args), argsDisposer, flags);

var result = Inner.ExecLeaseAsync(command, ToInnerLease(args, out var lease), argsDisposer, flags);
return lease != null ? ReturnAfterResult(result, lease) : result;
}

public Task<RedisResult> ExecuteAsync(string command, params object[] args) =>
Inner.ExecuteAsync(command, ToInner(args), CommandFlags.None);

Expand All @@ -419,6 +444,24 @@ public Task<RedisResult> 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<RedisResult> ScriptEvalAsync(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None)
{
if ((flags & CommandFlags.FireAndForget) != 0)
return Inner.ScriptEvalAsync(script, ToInnerCopy(args), flags);

var result = Inner.ScriptEvalAsync(script, ToInnerLease(args, out var lease), flags);
return lease != null ? ReturnAfterResult(result, lease) : result;
}

public Task<Lease<byte>?> ScriptEvalLeaseAsync(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None)
{
if ((flags & CommandFlags.FireAndForget) != 0)
return Inner.ScriptEvalLeaseAsync(script, ToInnerCopy(args), flags);

var result = Inner.ScriptEvalLeaseAsync(script, ToInnerLease(args, out var lease), flags);
return lease != null ? ReturnAfterResult(result, lease) : result;
}

public Task<RedisResult> 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: script, keys: ToInner(keys), values: values, flags: flags);
Expand All @@ -435,6 +478,24 @@ public Task<RedisResult> ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? ke
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
Inner.ScriptEvaluateAsync(hash, ToInner(keys), values, flags);

public Task<RedisResult> ScriptEvalReadOnlyAsync(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None)
{
if ((flags & CommandFlags.FireAndForget) != 0)
return Inner.ScriptEvalReadOnlyAsync(script, ToInnerCopy(args), flags);

var result = Inner.ScriptEvalReadOnlyAsync(script, ToInnerLease(args, out var lease), flags);
return lease != null ? ReturnAfterResult(result, lease) : result;
}

public Task<Lease<byte>?> ScriptEvalReadOnlyLeaseAsync(string script, ReadOnlyMemory<RedisKeyOrValue> args = default, CommandFlags flags = CommandFlags.None)
{
if ((flags & CommandFlags.FireAndForget) != 0)
return Inner.ScriptEvalReadOnlyLeaseAsync(script, ToInnerCopy(args), flags);

var result = Inner.ScriptEvalReadOnlyLeaseAsync(script, ToInnerLease(args, out var lease), flags);
return lease != null ? ReturnAfterResult(result, lease) : result;
}

public Task<RedisResult> 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: script, keys: ToInner(keys), values: values, flags: flags);
Expand Down Expand Up @@ -947,6 +1008,87 @@ protected RedisKey ToInnerOrDefault(RedisKey outer) =>
return args;
}

protected ReadOnlyMemory<RedisKeyOrValue> ToInnerCopy(ReadOnlyMemory<RedisKeyOrValue> outer)
{
if (outer.Length > 0)
{
RedisKeyOrValue[] inner = [];

var span = outer.Span;
var i = 0;
foreach (ref readonly var item in span)
{
var key = item.Key;
if (!key.IsNull)
{
inner = new RedisKeyOrValue[outer.Length];
inner[i] = ToInner(key);
span.Slice(0, i).CopyTo(inner);
break;
}
i++;
}

if (inner.Length > 0)
{
i++;
foreach (ref readonly var item in span.Slice(i))
{
var key = item.Key;
inner[i++] = key.IsNull ? item : ToInner(key);
}

return inner;
}
}

return outer;
}

protected ReadOnlyMemory<RedisKeyOrValue> ToInnerLease(ReadOnlyMemory<RedisKeyOrValue> outer, out RedisKeyOrValue[]? lease)
{
lease = null;
var length = outer.Length;
if (length > 0)
{
var span = outer.Span;
var i = 0;
foreach (ref readonly var item in span)
{
var key = item.Key;
if (!key.IsNull)
{
lease = ArrayPool<RedisKeyOrValue>.Shared.Rent(length);
lease[i] = ToInner(key);
span.Slice(0, i).CopyTo(lease);
break;
}
i++;
}

if (lease != null)
{
i++;
foreach (ref readonly var item in span.Slice(i))
{
var key = item.Key;
lease[i++] = key.IsNull ? item : ToInner(key);
}

return lease.AsMemory(0, length);
}
}

return outer;
}

private static async Task<T> ReturnAfterResult<T>(Task<T> task, RedisKeyOrValue[] lease)
{
var result = await task;
ArrayPool<RedisKeyOrValue>.Shared.Return(lease, clearArray: true);
return result;
}

[return: NotNullIfNotNull("outer")]
protected RedisKey[]? ToInner(RedisKey[]? outer)
{
Expand Down
Loading
Loading