Skip to content

Commit

Permalink
Add SortedSetCount to IGarnetApi
Browse files Browse the repository at this point in the history
Use it in test custom transaction (stored proc)
  • Loading branch information
badrishc committed Jan 30, 2025
1 parent d148f51 commit 6e971b4
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 0 deletions.
4 changes: 4 additions & 0 deletions libs/server/API/GarnetApiObjectCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public GarnetStatus SortedSetMPop(ReadOnlySpan<ArgSlice> keys, int count, bool l
public GarnetStatus SortedSetPop(ArgSlice key, out (ArgSlice member, ArgSlice score)[] pairs, int count = 1, bool lowScoresFirst = true)
=> storageSession.SortedSetPop(key, count, lowScoresFirst, out pairs, ref objectContext);

/// <inheritdoc />
public GarnetStatus SortedSetCount(ArgSlice key, ArgSlice minScore, ArgSlice maxScore, out int numElements)
=> storageSession.SortedSetCount(key, minScore, maxScore, out numElements, ref objectContext);

/// <inheritdoc />
public GarnetStatus SortedSetCount(byte[] key, ref ObjectInput input, ref GarnetObjectStoreOutput output)
=> storageSession.SortedSetCount(key, ref input, ref output, ref objectContext);
Expand Down
7 changes: 7 additions & 0 deletions libs/server/API/GarnetWatchApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ public GarnetStatus SortedSetLength(byte[] key, ref ObjectInput input, out Objec
return garnetApi.SortedSetLength(key, ref input, out output);
}

/// <inheritdoc />
public GarnetStatus SortedSetCount(ArgSlice key, ArgSlice minScore, ArgSlice maxScore, out int numElements)
{
garnetApi.WATCH(key, StoreType.Object);
return garnetApi.SortedSetCount(key, minScore, maxScore, out numElements);
}

/// <inheritdoc />
public GarnetStatus SortedSetCount(byte[] key, ref ObjectInput input, ref GarnetObjectStoreOutput output)
{
Expand Down
10 changes: 10 additions & 0 deletions libs/server/API/IGarnetApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,16 @@ public interface IGarnetReadApi
/// <returns></returns>
GarnetStatus SortedSetScores(byte[] key, ref ObjectInput input, ref GarnetObjectStoreOutput outputFooter);

/// <summary>
/// Returns the number of elements in the sorted set at key with a score between min and max.
/// </summary>
/// <param name="key">Key</param>
/// <param name="minScore">Min Score</param>
/// <param name="maxScore">Max score</param>
/// <param name="numElements">Number of elements</param>
/// <returns></returns>
GarnetStatus SortedSetCount(ArgSlice key, ArgSlice minScore, ArgSlice maxScore, out int numElements);

/// <summary>
/// Returns the number of elements in the sorted set at key with a score between min and max.
/// </summary>
Expand Down
40 changes: 40 additions & 0 deletions libs/server/Storage/Session/ObjectStore/SortedSetOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,46 @@ public GarnetStatus SortedSetPop<TObjectContext>(byte[] key, ref ObjectInput inp
where TObjectContext : ITsavoriteContext<byte[], IGarnetObject, ObjectInput, GarnetObjectStoreOutput, long, ObjectSessionFunctions, ObjectStoreFunctions, ObjectStoreAllocator>
=> RMWObjectStoreOperationWithOutput(key, ref input, ref objectStoreContext, ref outputFooter);

/// <summary>
/// Returns the number of elements in the sorted set at key with a score between min and max.
/// </summary>
/// <typeparam name="TObjectContext"></typeparam>
/// <param name="key"></param>
/// <param name="minScore"></param>
/// <param name="maxScore"></param>
/// <param name="numElements"></param>
/// <param name="objectContext"></param>
/// <returns></returns>
public unsafe GarnetStatus SortedSetCount<TObjectContext>(ArgSlice key, ArgSlice minScore, ArgSlice maxScore, out int numElements, ref TObjectContext objectContext)
where TObjectContext : ITsavoriteContext<byte[], IGarnetObject, ObjectInput, GarnetObjectStoreOutput, long, ObjectSessionFunctions, ObjectStoreFunctions, ObjectStoreAllocator>
{
numElements = 0;
if (key.Length == 0)
return GarnetStatus.OK;

// Prepare the parse state
parseState.InitializeWithArguments(minScore, maxScore);

// Prepare the input
var header = new RespInputHeader(GarnetObjectType.SortedSet) { SortedSetOp = SortedSetOperation.ZCOUNT };
var input = new ObjectInput(header, ref parseState);

const int outputContainerSize = 32; // 3 for HEADER + CRLF + 20 for ascii long
var outputContainer = stackalloc byte[outputContainerSize];
var outputFooter = new GarnetObjectStoreOutput { SpanByteAndMemory = new SpanByteAndMemory(outputContainer, outputContainerSize) };

var status = ReadObjectStoreOperationWithOutput(key.ToArray(), ref input, ref objectContext, ref outputFooter);

if (status == GarnetStatus.OK)
{
Debug.Assert(*outputContainer == (byte)':');
var read = TryProcessRespSimple64IntOutput(outputFooter, out var value);
numElements = read ? (int)value : default;
Debug.Assert(read);
}
return status;
}

/// <summary>
/// Returns the number of elements in the sorted set at key with a score between min and max.
/// </summary>
Expand Down
31 changes: 31 additions & 0 deletions test/Garnet.test/Extensions/SortedSetCountTxn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Garnet.common;
using Garnet.server;
using Tsavorite.core;

namespace Garnet
{
sealed class SortedSetCountTxn : CustomTransactionProcedure
{
public override bool Prepare<TGarnetReadApi>(TGarnetReadApi api, ref CustomProcedureInput input)
{
int offset = 0;
AddKey(GetNextArg(ref input, ref offset), LockType.Exclusive, true);
return true;
}

public override unsafe void Main<TGarnetApi>(TGarnetApi api, ref CustomProcedureInput input, ref MemoryResult<byte> output)
{
int offset = 0;
var key = GetNextArg(ref input, ref offset);
var minScore = GetNextArg(ref input, ref offset);
var maxScore = GetNextArg(ref input, ref offset);

var status = api.SortedSetCount(key, minScore, maxScore, out int numElements);

WriteSimpleString(ref output, numElements.ToString());
}
}
}
19 changes: 19 additions & 0 deletions test/Garnet.test/RespCustomCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,25 @@ public void RateLimiterTest()
ClassicAssert.AreEqual("ALLOWED", result.ToString());
}

[Test]
public void SortedSetCountTxn()
{
server.Register.NewTransactionProc("SORTEDSETCOUNT", () => new SortedSetCountTxn(), new RespCommandsInfo { Arity = 4 });

using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
var db = redis.GetDatabase(0);

// Add entries to sorted set
for (var i = 1; i < 10; i++)
{
db.SortedSetAdd("key1", $"field{i}", i);
}

// Run transaction to get count of elements in given range of sorted set
var result = db.Execute("SORTEDSETCOUNT", "key1", 5, 10);
ClassicAssert.AreEqual(5, (int)result);
}

[Test]
public void CustomProcInvokingCustomCmdTest()
{
Expand Down

0 comments on commit 6e971b4

Please sign in to comment.