Skip to content

Commit 0b6abaf

Browse files
authored
Support Triggers and Functions (#145)
* strart working on RG commands * delete interfaces * test * Support TFunction Delete * uncomment tests * fix test * fix tests * Support TFunctionList * TestCommandBuilder * Support FTCall Sync * async FTCALL * Cleaning * review fixes * wip * TryDeleteLib * Try to delete all optional libs before test and more fixes * fix test * use saved strings
1 parent dfb621d commit 0b6abaf

File tree

7 files changed

+459
-0
lines changed

7 files changed

+459
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using NRedisStack.RedisStackCommands;
2+
using NRedisStack.Gears.Literals;
3+
namespace NRedisStack
4+
{
5+
6+
public static class GearsCommandBuilder
7+
{
8+
public static SerializedCommand TFunctionLoad(string libraryCode, bool replace = false, string? config = null)
9+
{
10+
var args = new List<object>() { GearsArgs.LOAD };
11+
12+
if (replace)
13+
{
14+
args.Add(GearsArgs.REPLACE);
15+
}
16+
17+
if (config != null)
18+
{
19+
args.Add(GearsArgs.CONFIG);
20+
args.Add(config);
21+
}
22+
args.Add(libraryCode);
23+
return new SerializedCommand(RG.TFUNCTION, args);
24+
}
25+
26+
public static SerializedCommand TFunctionDelete(string libraryName)
27+
{
28+
return new SerializedCommand(RG.TFUNCTION, GearsArgs.DELETE, libraryName);
29+
}
30+
31+
public static SerializedCommand TFunctionList(bool withCode = false, int verbose = 0, string? libraryName = null)
32+
{
33+
var args = new List<object>() { GearsArgs.LIST };
34+
35+
if (withCode)
36+
{
37+
args.Add(GearsArgs.WITHCODE);
38+
}
39+
40+
if (verbose > 0 && verbose < 4)
41+
{
42+
args.Add(new string('v', verbose));
43+
}
44+
else if (verbose != 0) // verbose == 0 is the default so we don't need to throw an error
45+
{
46+
throw new ArgumentOutOfRangeException(nameof(verbose), "verbose must be between 1 and 3");
47+
}
48+
49+
if (libraryName != null)
50+
{
51+
args.Add(GearsArgs.LIBRARY);
52+
args.Add(libraryName);
53+
}
54+
55+
return new SerializedCommand(RG.TFUNCTION, args);
56+
}
57+
58+
public static SerializedCommand TFCall(string libraryName, string functionName, string[]? keys = null, string[]? args = null, bool async = false)
59+
{
60+
string command = async ? RG.TFCALLASYNC : RG.TFCALL;
61+
var commandArgs = new List<object>() {$"{libraryName}.{functionName}"};
62+
63+
if (keys != null)
64+
{
65+
commandArgs.Add(keys.Length);
66+
commandArgs.AddRange(keys);
67+
}
68+
else
69+
{
70+
commandArgs.Add(0);
71+
}
72+
73+
if (args != null)
74+
{
75+
commandArgs.AddRange(args);
76+
}
77+
78+
return new SerializedCommand(command, commandArgs);
79+
}
80+
}
81+
}
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using StackExchange.Redis;
2+
namespace NRedisStack
3+
{
4+
5+
public static class GearsCommands //: GearsCommandsAsync, IGearsCommands
6+
{
7+
8+
/// <summary>
9+
/// Load a new library to RedisGears.
10+
/// </summary>
11+
/// <param name="libraryCode">the library code.</param>
12+
/// <param name="config">a string representation of a JSON object
13+
/// that will be provided to the library on load time,
14+
/// for more information refer to
15+
/// <see href="https://github.com/RedisGears/RedisGears/blob/master/docs/function_advance_topics.md#library-configuration">
16+
/// library configuration</see></param>
17+
/// <param name="replace">an optional argument, instructs RedisGears to replace the function if its already exists.</param>
18+
/// <returns><see langword="true"/> if everything was done correctly, Error otherwise.</returns>
19+
/// <remarks><seealso href="https://redis.io/commands/tfunction-load/"/></remarks> //TODO: check this link when it's available
20+
public static bool TFunctionLoad(this IDatabase db, string libraryCode, bool replace = false, string? config = null)
21+
{
22+
return db.Execute(GearsCommandBuilder.TFunctionLoad(libraryCode, replace, config)).OKtoBoolean();
23+
}
24+
25+
/// <summary>
26+
/// Delete a library from RedisGears.
27+
/// </summary>
28+
/// <param name="libraryName">the name of the library to delete.</param>
29+
/// <returns><see langword="true"/> if the library was deleted successfully, Error otherwise.</returns>
30+
/// <remarks><seealso href="https://redis.io/commands/tfunction-delete/"/></remarks> //TODO: check this link when it's available
31+
public static bool TFunctionDelete(this IDatabase db, string libraryName)
32+
{
33+
return db.Execute(GearsCommandBuilder.TFunctionDelete(libraryName)).OKtoBoolean();
34+
}
35+
36+
/// <summary>
37+
/// List the functions with additional information about each function.
38+
/// </summary>
39+
/// <param name="withCode">Show libraries code.</param>
40+
/// <param name="verbose">output verbosity level, higher number will increase verbosity level</param>
41+
/// <param name="libraryName">specifying a library name (can be used
42+
/// multiple times to show multiple libraries in a single command)</param>
43+
/// <returns>Information about the requested libraries.</returns>
44+
/// <remarks><seealso href="https://redis.io/commands/tfunction-list/"/></remarks> //TODO: check this link when it's available
45+
public static Dictionary<string, RedisResult>[] TFunctionList(this IDatabase db, bool withCode = false, int verbose = 0, string? libraryName = null)
46+
{
47+
return db.Execute(GearsCommandBuilder.TFunctionList(withCode, verbose, libraryName)).ToDictionarys();
48+
}
49+
50+
/// <summary>
51+
/// Trigger a sync or async (Coroutine) function.
52+
/// </summary>
53+
/// <param name="libraryName">The library name contains the function.</param>
54+
/// <param name="functionName">The function name to run.</param>
55+
/// <param name="keys">keys that will be touched by the function.</param>
56+
/// <param name="args">Additional argument to pass to the function.</param>
57+
/// <param name="async">If true, Invoke an async function (Coroutine).</param>
58+
/// <returns>The return value from the sync & async function on error in case of failure.</returns>
59+
/// <remarks><seealso href="https://redis.io/commands/tfcall"/></remarks> //TODO: check this link when it's available
60+
/// <remarks><seealso href="https://redis.io/commands/tfcallasync"/></remarks> //TODO: check this link when it's available
61+
public static RedisResult TFCall(this IDatabase db, string libraryName, string functionName, string[]? keys = null, string[]? args = null, bool async = false)
62+
{
63+
return db.Execute(GearsCommandBuilder.TFCall(libraryName, functionName, keys, args, async));
64+
}
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using StackExchange.Redis;
2+
namespace NRedisStack
3+
{
4+
5+
public static class GearsCommandsAsync //: IGearsCommandsAsync
6+
{
7+
/// <summary>
8+
/// Load a new library to RedisGears.
9+
/// </summary>
10+
/// <param name="libraryCode">the library code.</param>
11+
/// <param name="config">a string representation of a JSON object
12+
/// that will be provided to the library on load time,
13+
/// for more information refer to
14+
/// <see href="https://github.com/RedisGears/RedisGears/blob/master/docs/function_advance_topics.md#library-configuration">
15+
/// library configuration</see></param>
16+
/// <param name="replace">an optional argument, instructs RedisGears to replace the function if its already exists.</param>
17+
/// <returns><see langword="true"/> if everything was done correctly, Error otherwise.</returns>
18+
/// <remarks><seealso href="https://redis.io/commands/"/></remarks> //TODO: add link to the command when it's available
19+
public static async Task<bool> TFunctionLoadAsync(this IDatabase db, string libraryCode, string? config = null, bool replace = false)
20+
{
21+
return (await db.ExecuteAsync(GearsCommandBuilder.TFunctionLoad(libraryCode, replace, config))).OKtoBoolean();
22+
}
23+
24+
/// <summary>
25+
/// Delete a library from RedisGears.
26+
/// </summary>
27+
/// <param name="libraryName">the name of the library to delete.</param>
28+
/// <returns><see langword="true"/> if the library was deleted successfully, Error otherwise.</returns>
29+
/// <remarks><seealso href="https://redis.io/commands/"/></remarks> //TODO: add link to the command when it's available
30+
public static async Task<bool> TFunctionDeleteAsync(this IDatabase db, string libraryName)
31+
{
32+
return (await db.ExecuteAsync(GearsCommandBuilder.TFunctionDelete(libraryName))).OKtoBoolean();
33+
}
34+
35+
/// <summary>
36+
/// List the functions with additional information about each function.
37+
/// </summary>
38+
/// <param name="withCode">Show libraries code.</param>
39+
/// <param name="verbose">output verbosity level, higher number will increase verbosity level</param>
40+
/// <param name="libraryName">specifying a library name (can be used
41+
/// multiple times to show multiple libraries in a single command)</param>
42+
/// <returns>Information about the requested libraries.</returns>
43+
/// <remarks><seealso href="https://redis.io/commands/"/></remarks> //TODO: add link to the command when it's available
44+
public static async Task<Dictionary<string, RedisResult>[]> TFunctionListAsync(this IDatabase db, bool withCode = false, int verbose = 0, string? libraryName = null)
45+
{
46+
return (await db.ExecuteAsync(GearsCommandBuilder.TFunctionList(withCode, verbose, libraryName))).ToDictionarys();
47+
}
48+
49+
/// <summary>
50+
/// Invoke a sync or async (Coroutine) function.
51+
/// </summary>
52+
/// <param name="libraryName">The library name contains the function.</param>
53+
/// <param name="functionName">The function name to run.</param>
54+
/// <param name="keys">keys that will be touched by the function.</param>
55+
/// <param name="args">Additional argument to pass to the function.</param>
56+
/// <param name="async">If true, Invoke an async function (Coroutine).</param>
57+
/// <returns>The return value from the sync & async function on error in case of failure.</returns>
58+
/// <remarks><seealso href="https://redis.io/commands/"/></remarks> //TODO: add link to the command when it's available
59+
public static async Task<RedisResult> TFCallAsync(this IDatabase db, string libraryName, string functionName, string[]? keys = null, string[]? args = null, bool async = false)
60+
{
61+
return await db.ExecuteAsync(GearsCommandBuilder.TFCall(libraryName, functionName, keys, args, async));
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace NRedisStack.Gears.Literals
2+
{
3+
internal class GearsArgs
4+
{
5+
public const string CONFIG = "CONFIG";
6+
public const string REPLACE = "REPLACE";
7+
public const string LOAD = "LOAD";
8+
public const string DELETE = "DELETE";
9+
public const string LIST = "LIST";
10+
public const string WITHCODE = "WITHCODE";
11+
public const string LIBRARY = "LIBRARY";
12+
}
13+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace NRedisStack.Gears.Literals
2+
{
3+
/// <summary>
4+
/// RedisGears command literals
5+
/// </summary>
6+
internal class RG
7+
{
8+
public const string TFUNCTION = "TFUNCTION";
9+
public const string TFCALL = "TFCALL";
10+
public const string TFCALLASYNC = "TFCALLASYNC";
11+
}
12+
}

src/NRedisStack/ResponseParser.cs

+12
Original file line numberDiff line numberDiff line change
@@ -613,5 +613,17 @@ public static IEnumerable<HashSet<string>> ToHashSets(this RedisResult result)
613613

614614
return sets;
615615
}
616+
617+
public static Dictionary<string, RedisResult>[] ToDictionarys(this RedisResult result)
618+
{
619+
var resArr = (RedisResult[])result!;
620+
var dicts = new Dictionary<string, RedisResult>[resArr.Length];
621+
for (int i = 0; i < resArr.Length; i++)
622+
{
623+
dicts[i] = resArr[i].ToDictionary();
624+
}
625+
626+
return dicts;
627+
}
616628
}
617629
}

0 commit comments

Comments
 (0)