Skip to content

Support JSON.MSET Command #131

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

Merged
merged 9 commits into from
May 10, 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
15 changes: 12 additions & 3 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
USER_NAME: ${{ secrets.USER_NAME }}
PASSWORD: ${{ secrets.PASSWORD }}
ENDPOINT: ${{ secrets.ENDPOINT }}
REDIS_VERSION: ${{ matrix.redis-stack-version }}
steps:
- uses: actions/checkout@v3
- name: .NET Core 6
Expand All @@ -45,14 +46,22 @@ jobs:
echo "${{secrets.REDIS_USER_CRT}}" > tests/NRedisStack.Tests/bin/Debug/net6.0/redis_user.crt
echo "${{secrets.REDIS_USER_PRIVATE_KEY}}" > tests/NRedisStack.Tests/bin/Debug/net6.0/redis_user_private.key
ls -R
dotnet test -f net6.0 --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
if [ "$REDIS_VERSION" != "edge" ]; then
dotnet test -f net6.0 --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover --filter Category!=edge
else
dotnet test -f net6.0 --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
fi
- name: Test
run: |
echo "${{secrets.REDIS_CA_PEM}}" > tests/NRedisStack.Tests/bin/Debug/net7.0/redis_ca.pem
echo "${{secrets.REDIS_USER_CRT}}" > tests/NRedisStack.Tests/bin/Debug/net7.0/redis_user.crt
echo "${{secrets.REDIS_USER_PRIVATE_KEY}}" > tests/NRedisStack.Tests/bin/Debug/net7.0/redis_user_private.key
ls -R
dotnet test -f net7.0 --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
if [ "$REDIS_VERSION" != "edge" ]; then
dotnet test -f net7.0 --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover --filter Category!=edge
else
dotnet test -f net7.0 --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
fi
- name: Codecov
uses: codecov/codecov-action@v3
with:
Expand Down Expand Up @@ -96,4 +105,4 @@ jobs:
shell: cmd
run: |
START wsl ./redis-stack-server-${{env.redis_stack_version}}/bin/redis-stack-server &
dotnet test -f net481 --no-build --verbosity normal
dotnet test -f net481 --no-build --verbosity normal --filter Category!=edge
30 changes: 30 additions & 0 deletions src/NRedisStack/Json/DataTypes/KeyValuePath.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Text.Json;

namespace NRedisStack.Json.DataTypes;

public struct KeyValuePath
{
public string Key { get; set; }
public object Value { get; set; }
public string Path { get; set; }

public KeyValuePath(string key, object value, string path = "$")
{
if (key == null || value == null)
{
throw new ArgumentNullException("Key and value cannot be null.");
}

Key = key;
Value = value;
Path = path;
}
public string[] ToArray()
{
if (Value is string)
{
return new string[] { Key, Path, Value.ToString() };
}
return new string[] { Key, Path, JsonSerializer.Serialize(Value) };
}
}
14 changes: 12 additions & 2 deletions src/NRedisStack/Json/IJsonCommands.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using StackExchange.Redis;
using NRedisStack.Json.DataTypes;
using StackExchange.Redis;

namespace NRedisStack;

Expand Down Expand Up @@ -206,7 +207,16 @@ public interface IJsonCommands
bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When.Always);

/// <summary>
/// Set json file from the provided file Path.
/// Sets or updates the JSON value of one or more keys.
/// </summary>
/// <param name="keyValuePathList">The key, The value to set and
/// The path to set within the key, must be > 1 </param>
/// <returns>The disposition of the command</returns>
/// <remarks><seealso href="https://redis.io/commands/json.mset"/></remarks>
bool MSet(KeyValuePath[] keyValuePathList);

/// <summary>
/// Sets or updates the JSON value of one or more keys.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="path">The path to set within the key.</param>
Expand Down
12 changes: 11 additions & 1 deletion src/NRedisStack/Json/IJsonCommandsAsync.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using StackExchange.Redis;
using NRedisStack.Json.DataTypes;
using StackExchange.Redis;

namespace NRedisStack;

Expand Down Expand Up @@ -205,6 +206,15 @@ public interface IJsonCommandsAsync
/// <remarks><seealso href="https://redis.io/commands/json.set"/></remarks>
Task<bool> SetAsync(RedisKey key, RedisValue path, RedisValue json, When when = When.Always);

/// <summary>
/// Sets or updates the JSON value of one or more keys.
/// </summary>
/// <param name="keyValuePathList">The key, The value to set and
/// The path to set within the key, must be > 1 </param>
/// <returns>The disposition of the command</returns>
/// <remarks><seealso href="https://redis.io/commands/json.mset"/></remarks>
Task<bool> MSetAsync(KeyValuePath[] keyValuePathList);

/// <summary>
/// Set json file from the provided file Path.
/// </summary>
Expand Down
12 changes: 11 additions & 1 deletion src/NRedisStack/Json/JsonCommandBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NRedisStack.Json.Literals;
using NRedisStack.Json.DataTypes;
using NRedisStack.Json.Literals;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;
using System.Text.Json;
Expand Down Expand Up @@ -28,6 +29,15 @@ public static SerializedCommand Set(RedisKey key, RedisValue path, RedisValue js
};
}

public static SerializedCommand MSet(KeyValuePath[] keyValuePathList)
{
if (keyValuePathList.Length < 1)
throw new ArgumentOutOfRangeException(nameof(keyValuePathList));

var args = keyValuePathList.SelectMany(x => x.ToArray()).ToArray();
return new SerializedCommand(JSON.MSET, args);
}

public static SerializedCommand StrAppend(RedisKey key, string value, string? path = null)
{
if (path == null)
Expand Down
9 changes: 8 additions & 1 deletion src/NRedisStack/Json/JsonCommands.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using StackExchange.Redis;
using NRedisStack.Json.DataTypes;
using StackExchange.Redis;
using System.Text.Json;
using System.Text.Json.Nodes;

Expand Down Expand Up @@ -39,6 +40,12 @@ public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When
return _db.Execute(JsonCommandBuilder.Set(key, path, json, when)).OKtoBoolean();
}

/// <inheritdoc/>
public bool MSet(KeyValuePath[] keyValuePathList)
{
return _db.Execute(JsonCommandBuilder.MSet(keyValuePathList)).OKtoBoolean();
}

/// <inheritdoc/>
public bool SetFromFile(RedisKey key, RedisValue path, string filePath, When when = When.Always)
{
Expand Down
8 changes: 7 additions & 1 deletion src/NRedisStack/Json/JsonCommandsAsync.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using StackExchange.Redis;
using NRedisStack.Json.DataTypes;
using StackExchange.Redis;
using System.Text.Json;
using System.Text.Json.Nodes;

Expand Down Expand Up @@ -143,6 +144,11 @@ public async Task<bool> SetAsync(RedisKey key, RedisValue path, RedisValue json,
return (await _db.ExecuteAsync(JsonCommandBuilder.Set(key, path, json, when))).OKtoBoolean();
}

public async Task<bool> MSetAsync(KeyValuePath[] keyValuePathList)
{
return (await _db.ExecuteAsync(JsonCommandBuilder.MSet(keyValuePathList))).OKtoBoolean();
}

public async Task<bool> SetFromFileAsync(RedisKey key, RedisValue path, string filePath, When when = When.Always)
{
if (!File.Exists(filePath))
Expand Down
1 change: 1 addition & 0 deletions src/NRedisStack/Json/Literals/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal class JSON
public const string FORGET = "JSON.FORGET";
public const string GET = "JSON.GET";
public const string MEMORY = "MEMORY";
public const string MSET = "JSON.MSET";
public const string MGET = "JSON.MGET";
public const string NUMINCRBY = "JSON.NUMINCRBY";
public const string NUMMULTBY = "JSON.NUMMULTBY";
Expand Down
59 changes: 58 additions & 1 deletion tests/NRedisStack.Tests/Json/JsonTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using StackExchange.Redis;
using Moq;
using NRedisStack.RedisStackCommands;

using NRedisStack.Json.DataTypes;

namespace NRedisStack.Tests;

Expand Down Expand Up @@ -725,6 +725,63 @@ public async Task GetAsync()
Assert.Equal(35, people[1]!.Age);
}

[Fact]
[Trait("Category","edge")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect. We'll remove this once it hits a versioned docker. Similar to the skipif in other tests for other clients.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be in docker & the main ubuntu package - the windows image downloads the tarball directly and installs it in WSL.

Copy link
Member

@slorello89 slorello89 May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a skip attribute that lets you skip a test you don't want to run, but it doesn't permit conditionals unfortunately, there are ways to setup some conditionals to override the behavior (several OSS projects do something of the like) but that's kind of overkill for something that needs a relatively simple filter like this.

public void MSet()
{
IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase());
var keys = CreateKeyNames(2);
var key1 = keys[0];
var key2 = keys[1];

KeyValuePath[] values = new[]
{
new KeyValuePath(key1, new { a = "hello" }),
new KeyValuePath(key2, new { a = "world" })
};
commands.MSet(values)
;
var result = commands.MGet(keys.Select(x => new RedisKey(x)).ToArray(), "$.a");

Assert.Equal("[\"hello\"]", result[0].ToString());
Assert.Equal("[\"world\"]", result[1].ToString());

// test errors:
Assert.Throws<ArgumentOutOfRangeException>(() => commands.MSet(new KeyValuePath[0]));

}

[Fact]
[Trait("Category","edge")]
public async Task MSetAsync()
{
IJsonCommandsAsync commands = new JsonCommands(redisFixture.Redis.GetDatabase());
var keys = CreateKeyNames(2);
var key1 = keys[0];
var key2 = keys[1];
KeyValuePath[] values = new[]
{
new KeyValuePath(key1, new { a = "hello" }),
new KeyValuePath(key2, new { a = "world" })
};
await commands.MSetAsync(values)
;
var result = await commands.MGetAsync(keys.Select(x => new RedisKey(x)).ToArray(), "$.a");

Assert.Equal("[\"hello\"]", result[0].ToString());
Assert.Equal("[\"world\"]", result[1].ToString());

// test errors:
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await commands.MSetAsync(new KeyValuePath[0]));
}

[Fact]
public void TestKeyValuePathErrors()
{
Assert.Throws<ArgumentNullException>(() => new KeyValuePath(null!, new { a = "hello" }));
Assert.Throws<ArgumentNullException>(() => new KeyValuePath("key", null!) );
}

[Fact]
public void MGet()
{
Expand Down