Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Triggers and Functions #145

Merged
merged 20 commits into from
Jul 2, 2023
Merged
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
81 changes: 81 additions & 0 deletions src/NRedisStack/Gears/GearsCommandBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using NRedisStack.RedisStackCommands;
using NRedisStack.Gears.Literals;
namespace NRedisStack
{

public static class GearsCommandBuilder
{
public static SerializedCommand TFunctionLoad(string libraryCode, bool replace = false, string? config = null)
{
var args = new List<object>() { GearsArgs.LOAD };

if (replace)
{
args.Add(GearsArgs.REPLACE);
}

if (config != null)
{
args.Add(GearsArgs.CONFIG);
args.Add(config);
}
args.Add(libraryCode);
return new SerializedCommand(RG.TFUNCTION, args);
}

public static SerializedCommand TFunctionDelete(string libraryName)
{
return new SerializedCommand(RG.TFUNCTION, GearsArgs.DELETE, libraryName);
}

public static SerializedCommand TFunctionList(bool withCode = false, int verbose = 0, string? libraryName = null)
{
var args = new List<object>() { GearsArgs.LIST };

if (withCode)
{
args.Add(GearsArgs.WITHCODE);
}

if (verbose > 0 && verbose < 4)
{
args.Add(new string('v', verbose));
}
else if (verbose != 0) // verbose == 0 is the default so we don't need to throw an error
{
throw new ArgumentOutOfRangeException(nameof(verbose), "verbose must be between 1 and 3");
}

if (libraryName != null)
{
args.Add(GearsArgs.LIBRARY);
args.Add(libraryName);
}

return new SerializedCommand(RG.TFUNCTION, args);
}

public static SerializedCommand TFCall(string libraryName, string functionName, string[]? keys = null, string[]? args = null, bool async = false)
{
string command = async ? RG.TFCALLASYNC : RG.TFCALL;
var commandArgs = new List<object>() {$"{libraryName}.{functionName}"};

if (keys != null)
{
commandArgs.Add(keys.Length);
commandArgs.AddRange(keys);
}
else
{
commandArgs.Add(0);
}

if (args != null)
{
commandArgs.AddRange(args);
}

return new SerializedCommand(command, commandArgs);
}
}
}
66 changes: 66 additions & 0 deletions src/NRedisStack/Gears/GearsCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using StackExchange.Redis;
namespace NRedisStack
{

public static class GearsCommands //: GearsCommandsAsync, IGearsCommands
{

/// <summary>
/// Load a new library to RedisGears.
/// </summary>
/// <param name="libraryCode">the library code.</param>
/// <param name="config">a string representation of a JSON object
/// that will be provided to the library on load time,
/// for more information refer to
/// <see href="https://github.com/RedisGears/RedisGears/blob/master/docs/function_advance_topics.md#library-configuration">
/// library configuration</see></param>
/// <param name="replace">an optional argument, instructs RedisGears to replace the function if its already exists.</param>
/// <returns><see langword="true"/> if everything was done correctly, Error otherwise.</returns>
/// <remarks><seealso href="https://redis.io/commands/tfunction-load/"/></remarks> //TODO: check this link when it's available
public static bool TFunctionLoad(this IDatabase db, string libraryCode, bool replace = false, string? config = null)
{
return db.Execute(GearsCommandBuilder.TFunctionLoad(libraryCode, replace, config)).OKtoBoolean();
}

/// <summary>
/// Delete a library from RedisGears.
/// </summary>
/// <param name="libraryName">the name of the library to delete.</param>
/// <returns><see langword="true"/> if the library was deleted successfully, Error otherwise.</returns>
/// <remarks><seealso href="https://redis.io/commands/tfunction-delete/"/></remarks> //TODO: check this link when it's available
public static bool TFunctionDelete(this IDatabase db, string libraryName)
{
return db.Execute(GearsCommandBuilder.TFunctionDelete(libraryName)).OKtoBoolean();
}

/// <summary>
/// List the functions with additional information about each function.
/// </summary>
/// <param name="withCode">Show libraries code.</param>
/// <param name="verbose">output verbosity level, higher number will increase verbosity level</param>
/// <param name="libraryName">specifying a library name (can be used
/// multiple times to show multiple libraries in a single command)</param>
/// <returns>Information about the requested libraries.</returns>
/// <remarks><seealso href="https://redis.io/commands/tfunction-list/"/></remarks> //TODO: check this link when it's available
public static Dictionary<string, RedisResult>[] TFunctionList(this IDatabase db, bool withCode = false, int verbose = 0, string? libraryName = null)
{
return db.Execute(GearsCommandBuilder.TFunctionList(withCode, verbose, libraryName)).ToDictionarys();
}

/// <summary>
/// Trigger a sync or async (Coroutine) function.
/// </summary>
/// <param name="libraryName">The library name contains the function.</param>
/// <param name="functionName">The function name to run.</param>
/// <param name="keys">keys that will be touched by the function.</param>
/// <param name="args">Additional argument to pass to the function.</param>
/// <param name="async">If true, Invoke an async function (Coroutine).</param>
/// <returns>The return value from the sync & async function on error in case of failure.</returns>
/// <remarks><seealso href="https://redis.io/commands/tfcall"/></remarks> //TODO: check this link when it's available
/// <remarks><seealso href="https://redis.io/commands/tfcallasync"/></remarks> //TODO: check this link when it's available
public static RedisResult TFCall(this IDatabase db, string libraryName, string functionName, string[]? keys = null, string[]? args = null, bool async = false)
{
return db.Execute(GearsCommandBuilder.TFCall(libraryName, functionName, keys, args, async));
}
}
}
64 changes: 64 additions & 0 deletions src/NRedisStack/Gears/GearsCommandsAsync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using StackExchange.Redis;
namespace NRedisStack
{

public static class GearsCommandsAsync //: IGearsCommandsAsync
{
/// <summary>
/// Load a new library to RedisGears.
/// </summary>
/// <param name="libraryCode">the library code.</param>
/// <param name="config">a string representation of a JSON object
/// that will be provided to the library on load time,
/// for more information refer to
/// <see href="https://github.com/RedisGears/RedisGears/blob/master/docs/function_advance_topics.md#library-configuration">
/// library configuration</see></param>
/// <param name="replace">an optional argument, instructs RedisGears to replace the function if its already exists.</param>
/// <returns><see langword="true"/> if everything was done correctly, Error otherwise.</returns>
/// <remarks><seealso href="https://redis.io/commands/"/></remarks> //TODO: add link to the command when it's available
public static async Task<bool> TFunctionLoadAsync(this IDatabase db, string libraryCode, string? config = null, bool replace = false)
{
return (await db.ExecuteAsync(GearsCommandBuilder.TFunctionLoad(libraryCode, replace, config))).OKtoBoolean();
}

/// <summary>
/// Delete a library from RedisGears.
/// </summary>
/// <param name="libraryName">the name of the library to delete.</param>
/// <returns><see langword="true"/> if the library was deleted successfully, Error otherwise.</returns>
/// <remarks><seealso href="https://redis.io/commands/"/></remarks> //TODO: add link to the command when it's available
public static async Task<bool> TFunctionDeleteAsync(this IDatabase db, string libraryName)
{
return (await db.ExecuteAsync(GearsCommandBuilder.TFunctionDelete(libraryName))).OKtoBoolean();
}

/// <summary>
/// List the functions with additional information about each function.
/// </summary>
/// <param name="withCode">Show libraries code.</param>
/// <param name="verbose">output verbosity level, higher number will increase verbosity level</param>
/// <param name="libraryName">specifying a library name (can be used
/// multiple times to show multiple libraries in a single command)</param>
/// <returns>Information about the requested libraries.</returns>
/// <remarks><seealso href="https://redis.io/commands/"/></remarks> //TODO: add link to the command when it's available
public static async Task<Dictionary<string, RedisResult>[]> TFunctionListAsync(this IDatabase db, bool withCode = false, int verbose = 0, string? libraryName = null)
{
return (await db.ExecuteAsync(GearsCommandBuilder.TFunctionList(withCode, verbose, libraryName))).ToDictionarys();
}

/// <summary>
/// Invoke a sync or async (Coroutine) function.
/// </summary>
/// <param name="libraryName">The library name contains the function.</param>
/// <param name="functionName">The function name to run.</param>
/// <param name="keys">keys that will be touched by the function.</param>
/// <param name="args">Additional argument to pass to the function.</param>
/// <param name="async">If true, Invoke an async function (Coroutine).</param>
/// <returns>The return value from the sync & async function on error in case of failure.</returns>
/// <remarks><seealso href="https://redis.io/commands/"/></remarks> //TODO: add link to the command when it's available
public static async Task<RedisResult> TFCallAsync(this IDatabase db, string libraryName, string functionName, string[]? keys = null, string[]? args = null, bool async = false)
{
return await db.ExecuteAsync(GearsCommandBuilder.TFCall(libraryName, functionName, keys, args, async));
}
}
}
13 changes: 13 additions & 0 deletions src/NRedisStack/Gears/Literals/CommandArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace NRedisStack.Gears.Literals
{
internal class GearsArgs
{
public const string CONFIG = "CONFIG";
public const string REPLACE = "REPLACE";
public const string LOAD = "LOAD";
public const string DELETE = "DELETE";
public const string LIST = "LIST";
public const string WITHCODE = "WITHCODE";
public const string LIBRARY = "LIBRARY";
}
}
12 changes: 12 additions & 0 deletions src/NRedisStack/Gears/Literals/Commands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace NRedisStack.Gears.Literals
{
/// <summary>
/// RedisGears command literals
/// </summary>
internal class RG
{
public const string TFUNCTION = "TFUNCTION";
public const string TFCALL = "TFCALL";
public const string TFCALLASYNC = "TFCALLASYNC";
}
}
12 changes: 12 additions & 0 deletions src/NRedisStack/ResponseParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -613,5 +613,17 @@ public static IEnumerable<HashSet<string>> ToHashSets(this RedisResult result)

return sets;
}

public static Dictionary<string, RedisResult>[] ToDictionarys(this RedisResult result)
{
var resArr = (RedisResult[])result!;
var dicts = new Dictionary<string, RedisResult>[resArr.Length];
for (int i = 0; i < resArr.Length; i++)
{
dicts[i] = resArr[i].ToDictionary();
}

return dicts;
}
}
}
Loading