Skip to content

Commit

Permalink
add SDIFFSTORE command.
Browse files Browse the repository at this point in the history
  • Loading branch information
tisilent committed Mar 31, 2024
1 parent 823dce4 commit 99071fd
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 9 deletions.
3 changes: 3 additions & 0 deletions libs/server/API/GarnetApiObjectCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ public GarnetStatus SetScan(ArgSlice key, long cursor, string match, int count,
/// <inheritdoc />
public GarnetStatus SetDiff(ArgSlice[] keys, out HashSet<byte[]> members)
=> storageSession.SetDiff(keys, out members, ref objectContext);

public GarnetStatus SetDiffStore(byte[] key, ArgSlice[] keys, out int count)
=> storageSession.SetDiffStore(key, keys, out count, ref objectContext);
#endregion

#region Hash Methods
Expand Down
9 changes: 9 additions & 0 deletions libs/server/API/IGarnetApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,15 @@ public interface IGarnetApi : IGarnetReadApi, IGarnetAdvancedApi
/// <returns></returns>
GarnetStatus SetPop(byte[] key, ArgSlice input, ref GarnetObjectStoreOutput outputFooter);

/// <summary>
/// Diff result store.
/// Returns the number of result set.
/// </summary>
/// <param name="key">destination</param>
/// <param name="keys"></param>
/// <param name="count"></param>
/// <returns></returns>
public GarnetStatus SetDiffStore(byte[] key, ArgSlice[] keys, out int count);
#endregion

#region List Methods
Expand Down
1 change: 1 addition & 0 deletions libs/server/Objects/Set/SetObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public enum SetOperation : byte
SCARD,
SSCAN,
SDIFF,
SDIFFSTORE,
}


Expand Down
51 changes: 51 additions & 0 deletions libs/server/Resp/Objects/SetCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -501,5 +501,56 @@ private unsafe bool SetDiff<TGarnetApi>(int count, byte* ptr, ref TGarnetApi sto

return true;
}

private unsafe bool SetDiffStore<TGarnetApi>(int count, byte* ptr, ref TGarnetApi storageApi)
where TGarnetApi : IGarnetApi
{
ptr += 17;

if (count < 3)
{
return AbortWithWrongNumberOfArguments("SDIFFSTORE", count);
}

// Get the key
if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead))
return false;

if (NetworkSingleKeySlotVerify(key, false))
{
var bufSpan = new ReadOnlySpan<byte>(recvBufferPtr, bytesRead);
if (!DrainCommands(bufSpan, count))
return false;
return true;
}

var keys = new ArgSlice[count - 2];
for (var i = 0; i < count - 2; i++)
{
keys[i] = default;
if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead))
return false;
}

if (NetworkKeyArraySlotVerify(ref keys, true))
{
var bufSpan = new ReadOnlySpan<byte>(recvBufferPtr, bytesRead);
if (!DrainCommands(bufSpan, count)) return false;
return true;
}

var status = storageApi.SetDiffStore(key, keys, out var output);

if (status == GarnetStatus.OK)
{
while (!RespWriteUtils.WriteInteger(output, ref dcurr, dend))
SendAndReset();
}

// Move input head
readHead = (int)(ptr - recvBufferPtr);

return true;
}
}
}
3 changes: 3 additions & 0 deletions libs/server/Resp/RespCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,9 @@ private RespCommand FastParseCommand(byte* ptr)
//[$5|SDIFF|] = 14 bytes = 8 (long) + 2 (ushort)
if (*(long*)ptr == 5064654379396445476L && *(ushort*)(ptr + 8) == 3398 && *(ptr + 10) == 10)
return (RespCommand.Set, (byte)SetOperation.SDIFF);
//[$10|SDIFFSTORE|] =
if (*(long*)ptr == 5279435965821104420L && *(ushort*)(ptr + 8) == 17990 && *(ptr + 16) == 10)
return (RespCommand.Set, (byte)SetOperation.SDIFFSTORE);
#endregion
}

Expand Down
1 change: 1 addition & 0 deletions libs/server/Resp/RespCommandsInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ public static RespCommandsInfo findCommand(RespCommand cmd, byte subCmd = 0)
{(byte)SetOperation.SPOP, new RespCommandsInfo("SPOP", RespCommand.Set, -2, null, (byte)SetOperation.SPOP) },
{(byte)SetOperation.SSCAN, new RespCommandsInfo("SSCAN", RespCommand.Set, -3, null, (byte)SetOperation.SSCAN) },
{(byte)SetOperation.SDIFF, new RespCommandsInfo("SDIFF", RespCommand.Set, -2, null, (byte)SetOperation.SDIFF) },
{(byte)SetOperation.SDIFFSTORE, new RespCommandsInfo("SDIFFSTORE", RespCommand.Set, -3, null, (byte)SetOperation.SDIFFSTORE) }
};

private static readonly Dictionary<RespCommand, RespCommandsInfo> customCommandsInfoMap = new Dictionary<RespCommand, RespCommandsInfo>
Expand Down
2 changes: 1 addition & 1 deletion libs/server/Resp/RespInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static HashSet<string> GetCommands()
// Pub/sub
"PUBLISH", "SUBSCRIBE", "PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE",
// Set
"SADD", "SREM", "SPOP", "SMEMBERS", "SCARD", "SSCAN", "SDIFF",
"SADD", "SREM", "SPOP", "SMEMBERS", "SCARD", "SSCAN", "SDIFF", "SDIFFSTORE",
//Scan ops
"DBSIZE", "KEYS","SCAN",
// Geospatial commands
Expand Down
1 change: 1 addition & 0 deletions libs/server/Resp/RespServerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ private bool ProcessArrayCommands<TGarnetApi>(ref TGarnetApi storageApi)
(RespCommand.Set, (byte)SetOperation.SPOP) => SetPop(count, ptr, ref storageApi),
(RespCommand.Set, (byte)SetOperation.SSCAN) => ObjectScan(count, ptr, GarnetObjectType.Set, ref storageApi),
(RespCommand.Set, (byte)SetOperation.SDIFF) => SetDiff(count, ptr, ref storageApi),
(RespCommand.Set, (byte)SetOperation.SDIFFSTORE) => SetDiffStore(count, ptr, ref storageApi),
_ => ProcessOtherCommands(count, ref storageApi),
};
return success;
Expand Down
59 changes: 58 additions & 1 deletion libs/server/Storage/Session/ObjectStore/SetOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ public GarnetStatus SetPop<TObjectContext>(byte[] key, ArgSlice input, ref Garne
/// <param name="members"></param>
/// <param name="objectContext"></param>
/// <returns></returns>
public GarnetStatus SetDiff<TObjectContext>(ArgSlice[] keys,out HashSet<byte[]> members, ref TObjectContext objectContext)
public GarnetStatus SetDiff<TObjectContext>(ArgSlice[] keys, out HashSet<byte[]> members, ref TObjectContext objectContext)
where TObjectContext : ITsavoriteContext<byte[], IGarnetObject, SpanByte, GarnetObjectStoreOutput, long>
{
members = default;
Expand Down Expand Up @@ -468,5 +468,62 @@ public GarnetStatus SetDiff<TObjectContext>(ArgSlice[] keys,out HashSet<byte[]>

return GarnetStatus.OK;
}

/// <summary>
/// Diff result store.
/// Returns the number of result set.
/// </summary>
/// <typeparam name="TObjectContext"></typeparam>
/// <param name="key">destination</param>
/// <param name="keys"></param>
/// <param name="count"></param>
/// <param name="objectContext"></param>
/// <returns></returns>
public GarnetStatus SetDiffStore<TObjectContext>(byte[] key, ArgSlice[] keys,out int count, ref TObjectContext objectContext)
where TObjectContext : ITsavoriteContext<byte[], IGarnetObject, SpanByte, GarnetObjectStoreOutput, long>
{
count = default;

if (key.Length == 0 || keys.Length == 0)
return GarnetStatus.OK;

var diffSet = _setDiff(keys, ref objectContext);

var asKey = scratchBufferManager.CreateArgSlice(key);
var asMembers = new ArgSlice[diffSet.Count];
for (var i = 0; i < diffSet.Count; i++)
{
asMembers[i] = scratchBufferManager.CreateArgSlice(diffSet.ElementAt(i));
}

var status = SetAdd(asKey, [.. asMembers], out var saddCount, ref objectContext);
count = saddCount;
return status;
}

private HashSet<byte[]> _setDiff<TObjectContext>(ArgSlice[] keys, ref TObjectContext objectContext)
where TObjectContext : ITsavoriteContext<byte[], IGarnetObject, SpanByte, GarnetObjectStoreOutput, long>
{
var result = new HashSet<byte[]>();

var status = GET(keys[0].Bytes, out var first, ref objectContext);
if (status == GarnetStatus.OK)
{
result.UnionWith(((SetObject)first.garnetObject).Set);
}

for (var i = 1; i < keys.Length; i++)
{
status = GET(keys[i].Bytes, out var next, ref objectContext);
if (status == GarnetStatus.OK)
{
var nextSet = ((SetObject)next.garnetObject).Set;
var interItems = result.Intersect(nextSet, nextSet.Comparer);
result.ExceptWith(interItems);
}
}

return result;
}
}
}
6 changes: 4 additions & 2 deletions libs/server/Transaction/TxnKeyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal int GetKeys(RespCommand command, int inputCount, out ReadOnlySpan<byte>
RespCommand.SortedSet => SortedSetObjectKeys(subCommand, inputCount),
RespCommand.List => ListObjectKeys(subCommand),
RespCommand.Hash => HashObjectKeys(subCommand),
RespCommand.Set => SetObjectKeys(subCommand),
RespCommand.Set => SetObjectKeys(subCommand, inputCount),
RespCommand.GET => SingleKey(1, false, LockType.Shared),
RespCommand.SET => SingleKey(1, false, LockType.Exclusive),
RespCommand.GETRANGE => SingleKey(1, false, LockType.Shared),
Expand Down Expand Up @@ -172,7 +172,7 @@ private int HashObjectKeys(byte subCommand)
};
}

private int SetObjectKeys(byte subCommand)
private int SetObjectKeys(byte subCommand, int inputCount)
{
return subCommand switch
{
Expand All @@ -181,6 +181,8 @@ private int SetObjectKeys(byte subCommand)
(byte)SetOperation.SREM => SingleKey(1, true, LockType.Exclusive),
(byte)SetOperation.SCARD => SingleKey(1, true, LockType.Exclusive),
(byte)SetOperation.SPOP => SingleKey(1, true, LockType.Exclusive),
(byte)SetOperation.SDIFF => ListKeys(inputCount, true, LockType.Shared),
(byte)SetOperation.SDIFFSTORE => ListKeys(inputCount, true, LockType.Exclusive),
_ => -1
};
}
Expand Down
41 changes: 36 additions & 5 deletions test/Garnet.test/RespSetTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using NUnit.Framework;
using StackExchange.Redis;
Expand Down Expand Up @@ -260,8 +261,8 @@ public void CanDoSdiff()
var key2 = "kye2";
var key2Value = new RedisValue[] { "c" };

db.SetAdd(key1, key1Value);
db.SetAdd(key2, key2Value);
_ = db.SetAdd(key1, key1Value);
_ = db.SetAdd(key2, key2Value);

var result = (RedisResult[])db.Execute("SDIFF", key1, key2);
Assert.AreEqual(3, result.Length);
Expand All @@ -275,7 +276,7 @@ public void CanDoSdiff()
var key3 = "key3";
var key3Value = new RedisValue[] { "a", "c", "e" };

db.SetAdd(key3, key3Value);
_ = db.SetAdd(key3, key3Value);

result = (RedisResult[])db.Execute("SDIFF", key1, key2, key3);
Assert.AreEqual(2, result.Length);
Expand All @@ -286,7 +287,6 @@ public void CanDoSdiff()
Assert.IsFalse(Array.Exists(result, t => t.ToString().Equals("c")));
Assert.IsFalse(Array.Exists(result, t => t.ToString().Equals("e")));
}

#endregion


Expand Down Expand Up @@ -460,7 +460,23 @@ public void CanDoSdiffLC()
lightClientRequest.SendCommand("SADD key3 a c e");
var response = lightClientRequest.SendCommand("SDIFF key1 key2 key3");
var expectedResponse = "*2\r\n$1\r\nb\r\n$1\r\nd\r\n";
Assert.AreEqual(response.AsSpan().Slice(0,expectedResponse.Length).ToArray(), expectedResponse);
Assert.AreEqual(expectedResponse, response.AsSpan().Slice(0,expectedResponse.Length).ToArray());
}

[Test]
public void CanDoSdiffStoreLC()
{
var lightClientRequest = TestUtils.CreateRequest();
lightClientRequest.SendCommand("SADD key1 a b c d");
lightClientRequest.SendCommand("SADD key2 c");
lightClientRequest.SendCommand("SADD key3 a c e");
var response = lightClientRequest.SendCommand("SDIFFSTORE key key1 key2 key3");
var expectedResponse = ":2\r\n";
Assert.AreEqual(expectedResponse, response.AsSpan().Slice(0, expectedResponse.Length).ToArray());

var membersResponse = lightClientRequest.SendCommand("SMEMBERS key");
expectedResponse = "*2\r\n$1\r\nb\r\n$1\r\nd\r\n";
Assert.AreEqual(expectedResponse, membersResponse.AsSpan().Slice(0, expectedResponse.Length).ToArray());
}
#endregion

Expand Down Expand Up @@ -506,6 +522,21 @@ public void CanDoSdiffWhenKeyDoesNotExisting()
var strResponse = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length);
Assert.AreEqual(expectedResponse, strResponse);
}

[Test]
public void CanDoSdiffStoreWhenMemberKeysNotExisting()
{
using var lightClientRequest = TestUtils.CreateRequest();
var response = lightClientRequest.SendCommand("SDIFFSTORE key key1 key2 key3");
var expectedResponse = ":0\r\n";
var strResponse = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length);
Assert.AreEqual(expectedResponse, strResponse);

var membersResponse = lightClientRequest.SendCommand("SMEMBERS key");
expectedResponse = "*0\r\n";
strResponse = Encoding.ASCII.GetString(membersResponse).Substring(0, expectedResponse.Length);
Assert.AreEqual(expectedResponse, strResponse);
}
#endregion


Expand Down

0 comments on commit 99071fd

Please sign in to comment.