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 CLIENT SETINFO Command #180

Merged
merged 33 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e496ec3
deprecated redisGraph
shacharPash Aug 24, 2023
ae8f952
supporting client SetInfo command
shacharPash Aug 31, 2023
d12bf7d
add tests
shacharPash Aug 31, 2023
f59afe0
check somthing
shacharPash Aug 31, 2023
f24cd97
check2
shacharPash Aug 31, 2023
2b065b1
Async
shacharPash Aug 31, 2023
072504e
try ConnectionMultiplexer.Connect("localhost");
shacharPash Aug 31, 2023
cdcb60a
Merge branch 'master' into Issue160/ClientSetInfo
shacharPash Sep 10, 2023
df78947
new format
shacharPash Sep 13, 2023
b61a324
Merge branch 'master' into Issue160/ClientSetInfo
shacharPash Sep 14, 2023
522bb91
fix core tests
shacharPash Sep 14, 2023
0ae22fb
try change to IDatabase instead of IDatabaseAsync
shacharPash Sep 18, 2023
c79fe40
add bool _setinfo to Auxiliary
shacharPash Sep 19, 2023
111e734
comment CoreTests
shacharPash Sep 19, 2023
f6cac53
fix Aux
shacharPash Sep 19, 2023
3409bfe
change test
shacharPash Sep 19, 2023
62f27cf
fix tests
shacharPash Sep 19, 2023
1122e69
not using pipeline
shacharPash Sep 19, 2023
8739251
move _setInfo = false;
shacharPash Sep 19, 2023
36749d7
delete unused key
shacharPash Sep 20, 2023
3822d06
if redis version less than 7.1.242 dont sent SETINFO
shacharPash Sep 20, 2023
fce926f
add sleep
shacharPash Sep 20, 2023
08b89c6
no sleep
shacharPash Sep 21, 2023
0e4876f
Merge branch 'master' into Issue160/ClientSetInfo
shacharPash Sep 27, 2023
5a0dd0b
Merge branch 'master' into Issue160/ClientSetInfo
shacharPash Sep 27, 2023
96dc3b2
Merge branch 'master' into Issue160/ClientSetInfo
shacharPash Oct 17, 2023
f72b28d
try setinfo to true in each test
shacharPash Oct 18, 2023
1c4dd72
reset info defaults in each test
shacharPash Oct 18, 2023
1bca657
skip cluster + send commands in pipeline
shacharPash Oct 18, 2023
ba79c14
delete comments
shacharPash Oct 18, 2023
3f548d5
add null test
shacharPash Oct 18, 2023
68b6f5f
compare and of strings in null test
shacharPash Oct 18, 2023
28bc9e0
fixes
shacharPash Oct 19, 2023
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
85 changes: 83 additions & 2 deletions src/NRedisStack/Auxiliary.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
using System.Xml.Linq;
using NRedisStack.Core;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

namespace NRedisStack
{
public static class Auxiliary
{
private static string? _libraryName = $"NRedisStack;.NET-{Environment.Version}";
private static bool _setInfo = true;
public static void ResetInfoDefaults()
{
_setInfo = true;
_libraryName = $"NRedisStack;.NET-{Environment.Version}";
}
public static List<object> MergeArgs(RedisKey key, params RedisValue[] items)
{
var args = new List<object>(items.Length + 1) { key };
Expand All @@ -26,20 +35,57 @@ public static object[] AssembleNonNullArguments(params object?[] arguments)
return args.ToArray();
}

// public static IDatabase GetDatabase(this ConnectionMultiplexer redis) => redis.GetDatabase("", "");

// TODO: add all the signatures of GetDatabase
public static IDatabase GetDatabase(this ConnectionMultiplexer redis,
string? LibraryName)
{
var _db = redis.GetDatabase();
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
if (LibraryName == null) // the user wants to disable the library name and version sending
_setInfo = false;

else // the user set his own the library name
_libraryName = $"NRedisStack({LibraryName});.NET-{Environment.Version})";

return _db;
}

private static void SetInfoInPipeline(this IDatabase db)
{
if (_libraryName == null) return;
Pipeline pipeline = new Pipeline(db);
_ = pipeline.Db.ClientSetInfoAsync(SetInfoAttr.LibraryName, _libraryName!);
_ = pipeline.Db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, GetNRedisStackVersion());
pipeline.Execute();
}

public static RedisResult Execute(this IDatabase db, SerializedCommand command)
{
var compareVersions = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints()[0]).Version.CompareTo(new Version(7, 1, 242));
if (_setInfo && compareVersions >= 0)
{
_setInfo = false;
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
db.SetInfoInPipeline();
}
return db.Execute(command.Command, command.Args);
}

public async static Task<RedisResult> ExecuteAsync(this IDatabaseAsync db, SerializedCommand command)
{
var compareVersions = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints()[0]).Version.CompareTo(new Version(7, 1, 242));
if (_setInfo && compareVersions >= 0)
{
_setInfo = false;
((IDatabase)db).SetInfoInPipeline();
}
return await db.ExecuteAsync(command.Command, command.Args);
}

public static List<RedisResult> ExecuteBroadcast(this IDatabaseAsync db, string command)
public static List<RedisResult> ExecuteBroadcast(this IDatabase db, string command)
=> db.ExecuteBroadcast(new SerializedCommand(command));

public static List<RedisResult> ExecuteBroadcast(this IDatabaseAsync db, SerializedCommand command)
public static List<RedisResult> ExecuteBroadcast(this IDatabase db, SerializedCommand command)
{
var redis = db.Multiplexer;
var endpoints = redis.GetEndPoints();
Expand Down Expand Up @@ -83,5 +129,40 @@ public async static Task<List<RedisResult>> ExecuteBroadcastAsync(this IDatabase
}
return results;
}

public static string GetNRedisStackVersion()
{
XDocument csprojDocument = GetCsprojDocument();
shacharPash marked this conversation as resolved.
Show resolved Hide resolved

// Find the Version element and get its value.
var versionElement = csprojDocument.Root!
.Descendants("Version")
.FirstOrDefault();

return versionElement!.Value;
}

public static string GetStackExchangeRedisVersion()
{
XDocument csprojDocument = GetCsprojDocument();

// Find the PackageReference element with Include="StackExchange.Redis" and get its Version attribute.
var stackExchangeRedisVersion = csprojDocument.Root!
.Descendants("PackageReference")
.Where(element => element.Attribute("Include")?.Value == "StackExchange.Redis")
.Select(element => element.Attribute("Version")?.Value)
.FirstOrDefault();

return stackExchangeRedisVersion!;
}

private static XDocument GetCsprojDocument()
{
string csprojFilePath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "src", "NRedisStack", "NRedisStack.csproj");

// Load the .csproj file.
var csprojDocument = XDocument.Load(csprojFilePath);
return csprojDocument;
}
}
}
22 changes: 22 additions & 0 deletions src/NRedisStack/CoreCommands/CoreCommandBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using NRedisStack.RedisStackCommands;
using NRedisStack.Core.Literals;
using NRedisStack.Core;

namespace NRedisStack
{

public static class CoreCommandBuilder
{
public static SerializedCommand ClientSetInfo(SetInfoAttr attr, string value)
{
string attrValue = attr switch
{
SetInfoAttr.LibraryName => CoreArgs.lib_name,
SetInfoAttr.LibraryVersion => CoreArgs.lib_ver,
_ => throw new System.NotImplementedException(),
};

return new SerializedCommand(RedisCoreCommands.CLIENT, RedisCoreCommands.SETINFO, attrValue, value);
}
}
}
22 changes: 22 additions & 0 deletions src/NRedisStack/CoreCommands/CoreCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using NRedisStack.Core;
using NRedisStack.Core.Literals;
using StackExchange.Redis;
namespace NRedisStack
{

public static class CoreCommands
{
/// <summary>
/// Sets information specific to the client or connection.
/// </summary>
/// <param name="attr">which attribute to set</param>
/// <param name="value">the attribute value</param>
/// <returns><see langword="true"/> if the attribute name was successfully set, Error otherwise.</returns>
/// <remarks><seealso href="https://redis.io/commands/client-setinfo/"/></remarks>
public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string value)
{

return db.Execute(CoreCommandBuilder.ClientSetInfo(attr, value)).OKtoBoolean();
}
}
}
20 changes: 20 additions & 0 deletions src/NRedisStack/CoreCommands/CoreCommandsAsync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using NRedisStack.Core;
using StackExchange.Redis;
namespace NRedisStack
{

public static class CoreCommandsAsync //: ICoreCommandsAsync
{
/// <summary>
/// Sets information specific to the client or connection.
/// </summary>
/// <param name="attr">which attribute to set</param>
/// <param name="value">the attribute value</param>
/// <returns><see langword="true"/> if the attribute name was successfully set, Error otherwise.</returns>
/// <remarks><seealso href="https://redis.io/commands/client-setinfo/"/></remarks>
public static async Task<bool> ClientSetInfoAsync(this IDatabaseAsync db, SetInfoAttr attr, string value)
{
return (await db.ExecuteAsync(CoreCommandBuilder.ClientSetInfo(attr, value))).OKtoBoolean();
}
}
}
12 changes: 12 additions & 0 deletions src/NRedisStack/CoreCommands/Enums/SetInfoAttr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace NRedisStack.Core;
public enum SetInfoAttr
{
/// <summary>
/// meant to hold the name of the client library that's in use.
/// </summary>
LibraryName,
/// <summary>
/// meant to hold the client library's version.
/// </summary>
LibraryVersion
}
8 changes: 8 additions & 0 deletions src/NRedisStack/CoreCommands/Literals/CommandArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace NRedisStack.Core.Literals
{
internal class CoreArgs
{
public const string lib_name = "LIB-NAME";
public const string lib_ver = "LIB-VER";
}
}
11 changes: 11 additions & 0 deletions src/NRedisStack/CoreCommands/Literals/Commands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace NRedisStack.Core.Literals
{
/// <summary>
/// Redis Core command literals
/// </summary>
internal class RedisCoreCommands
{
public const string CLIENT = "CLIENT";
public const string SETINFO = "SETINFO";
}
}
1 change: 0 additions & 1 deletion src/NRedisStack/Graph/IGraphCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

namespace NRedisStack
{

[Obsolete("RedisGraph support is deprecated as of Redis Stack 7.2 (https://redis.com/blog/redisgraph-eol/)")]
public interface IGraphCommands
{
Expand Down
120 changes: 120 additions & 0 deletions tests/NRedisStack.Tests/Core Commands/CoreTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using Xunit;
using NRedisStack.Core;
using NRedisStack;
using static NRedisStack.Auxiliary;
using StackExchange.Redis;
using System.Xml.Linq;
using System.Reflection;
using NRedisStack.RedisStackCommands;


namespace NRedisStack.Tests.Core;

public class CoreTests : AbstractNRedisStackTest, IDisposable
{
public CoreTests(RedisFixture redisFixture) : base(redisFixture) { }

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public void TestSetInfoDefaultValue()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase();
db.Execute("FLUSHALL");

db.Execute(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var info = db.Execute("CLIENT", "INFO").ToString();
Assert.EndsWith($"lib-name=NRedisStack;.NET-{Environment.Version} lib-ver={GetNRedisStackVersion()}\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public async Task TestSetInfoDefaultValueAsync()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase();
db.Execute("FLUSHALL");

await db.ExecuteAsync(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var info = (await db.ExecuteAsync("CLIENT", "INFO")).ToString();
Assert.EndsWith($"lib-name=NRedisStack;.NET-{Environment.Version} lib-ver={GetNRedisStackVersion()}\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public void TestSetInfoWithValue()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase("MyLibraryName;v1.0.0");
db.Execute("FLUSHALL");

db.Execute(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var info = db.Execute("CLIENT", "INFO").ToString();
Assert.EndsWith($"NRedisStack(MyLibraryName;v1.0.0);.NET-{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public async Task TestSetInfoWithValueAsync()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase("MyLibraryName;v1.0.0");
db.Execute("FLUSHALL");

await db.ExecuteAsync(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var info = (await db.ExecuteAsync("CLIENT", "INFO")).ToString();
Assert.EndsWith($"NRedisStack(MyLibraryName;v1.0.0);.NET-{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public void TestSetInfoNull()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase(null);

db.Execute("FLUSHALL");
var infoBefore = db.Execute("CLIENT", "INFO").ToString();
db.Execute(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var infoAfter = db.Execute("CLIENT", "INFO").ToString();
// Find the indices of "lib-name=" in the strings
int infoAfterLibNameIndex = infoAfter!.IndexOf("lib-name=");
int infoBeforeLibNameIndex = infoBefore!.IndexOf("lib-name=");

// Extract the sub-strings starting from "lib-name="
string infoAfterLibNameToEnd = infoAfter.Substring(infoAfterLibNameIndex);
string infoBeforeLibNameToEnd = infoBefore.Substring(infoBeforeLibNameIndex);

// Assert that the extracted sub-strings are equal
Assert.Equal(infoAfterLibNameToEnd, infoBeforeLibNameToEnd);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public async Task TestSetInfoNullAsync()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase(null);

db.Execute("FLUSHALL");
var infoBefore = (await db.ExecuteAsync("CLIENT", "INFO")).ToString();
await db.ExecuteAsync(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var infoAfter = (await db.ExecuteAsync("CLIENT", "INFO")).ToString();
// Find the indices of "lib-name=" in the strings
int infoAfterLibNameIndex = infoAfter!.IndexOf("lib-name=");
int infoBeforeLibNameIndex = infoBefore!.IndexOf("lib-name=");

// Extract the sub-strings starting from "lib-name="
string infoAfterLibNameToEnd = infoAfter.Substring(infoAfterLibNameIndex);
string infoBeforeLibNameToEnd = infoBefore.Substring(infoBeforeLibNameIndex);

// Assert that the extracted sub-strings are equal
Assert.Equal(infoAfterLibNameToEnd, infoBeforeLibNameToEnd);
}
}