Skip to content

Commit

Permalink
[Compatibility] Added ZINTER, ZINTERCARD, ZINTERSTORE command (#831)
Browse files Browse the repository at this point in the history
* Added ZINTER, ZINTERCARD, ZINTERSTORE command

* Format fix

* Test fix

* Added comments and docs

* Fix magic string

* Review commands

* Fixed review comments

---------

Co-authored-by: Tal Zaccai <[email protected]>
  • Loading branch information
Vijay-Nirmal and TalZaccai authored Dec 16, 2024
1 parent fad521e commit f1f9e51
Show file tree
Hide file tree
Showing 19 changed files with 1,364 additions and 6 deletions.
168 changes: 168 additions & 0 deletions libs/resources/RespCommandsDocs.json
Original file line number Diff line number Diff line change
Expand Up @@ -5817,6 +5817,174 @@
}
]
},
{
"Command": "ZINTER",
"Name": "ZINTER",
"Summary": "Returns the intersect of multiple sorted sets.",
"Group": "SortedSet",
"Complexity": "O(N*K)\u002BO(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.",
"Arguments": [
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "NUMKEYS",
"DisplayText": "numkeys",
"Type": "Integer"
},
{
"TypeDiscriminator": "RespCommandKeyArgument",
"Name": "KEY",
"DisplayText": "key",
"Type": "Key",
"ArgumentFlags": "Multiple",
"KeySpecIndex": 0
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "WEIGHT",
"DisplayText": "weight",
"Type": "Integer",
"Token": "WEIGHTS",
"ArgumentFlags": "Optional, Multiple"
},
{
"TypeDiscriminator": "RespCommandContainerArgument",
"Name": "AGGREGATE",
"Type": "OneOf",
"Token": "AGGREGATE",
"ArgumentFlags": "Optional",
"Arguments": [
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "SUM",
"DisplayText": "sum",
"Type": "PureToken",
"Token": "SUM"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "MIN",
"DisplayText": "min",
"Type": "PureToken",
"Token": "MIN"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "MAX",
"DisplayText": "max",
"Type": "PureToken",
"Token": "MAX"
}
]
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "WITHSCORES",
"DisplayText": "withscores",
"Type": "PureToken",
"Token": "WITHSCORES",
"ArgumentFlags": "Optional"
}
]
},
{
"Command": "ZINTERCARD",
"Name": "ZINTERCARD",
"Summary": "Returns the number of members of the intersect of multiple sorted sets.",
"Group": "SortedSet",
"Complexity": "O(N*K) worst case with N being the smallest input sorted set, K being the number of input sorted sets.",
"Arguments": [
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "NUMKEYS",
"DisplayText": "numkeys",
"Type": "Integer"
},
{
"TypeDiscriminator": "RespCommandKeyArgument",
"Name": "KEY",
"DisplayText": "key",
"Type": "Key",
"ArgumentFlags": "Multiple",
"KeySpecIndex": 0
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "LIMIT",
"DisplayText": "limit",
"Type": "Integer",
"Token": "LIMIT",
"ArgumentFlags": "Optional"
}
]
},
{
"Command": "ZINTERSTORE",
"Name": "ZINTERSTORE",
"Summary": "Stores the intersect of multiple sorted sets in a key.",
"Group": "SortedSet",
"Complexity": "O(N*K)\u002BO(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.",
"Arguments": [
{
"TypeDiscriminator": "RespCommandKeyArgument",
"Name": "DESTINATION",
"DisplayText": "destination",
"Type": "Key",
"KeySpecIndex": 0
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "NUMKEYS",
"DisplayText": "numkeys",
"Type": "Integer"
},
{
"TypeDiscriminator": "RespCommandKeyArgument",
"Name": "KEY",
"DisplayText": "key",
"Type": "Key",
"ArgumentFlags": "Multiple",
"KeySpecIndex": 1
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "WEIGHT",
"DisplayText": "weight",
"Type": "Integer",
"Token": "WEIGHTS",
"ArgumentFlags": "Optional, Multiple"
},
{
"TypeDiscriminator": "RespCommandContainerArgument",
"Name": "AGGREGATE",
"Type": "OneOf",
"Token": "AGGREGATE",
"ArgumentFlags": "Optional",
"Arguments": [
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "SUM",
"DisplayText": "sum",
"Type": "PureToken",
"Token": "SUM"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "MIN",
"DisplayText": "min",
"Type": "PureToken",
"Token": "MIN"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "MAX",
"DisplayText": "max",
"Type": "PureToken",
"Token": "MAX"
}
]
}
]
},
{
"Command": "ZLEXCOUNT",
"Name": "ZLEXCOUNT",
Expand Down
82 changes: 82 additions & 0 deletions libs/resources/RespCommandsInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -4284,6 +4284,88 @@
}
]
},
{
"Command": "ZINTER",
"Name": "ZINTER",
"Arity": -3,
"Flags": "MovableKeys, ReadOnly",
"AclCategories": "Read, SortedSet, Slow",
"KeySpecifications": [
{
"BeginSearch": {
"TypeDiscriminator": "BeginSearchIndex",
"Index": 1
},
"FindKeys": {
"TypeDiscriminator": "FindKeysKeyNum",
"KeyNumIdx": 0,
"FirstKey": 1,
"KeyStep": 1
},
"Flags": "RO, Access"
}
]
},
{
"Command": "ZINTERCARD",
"Name": "ZINTERCARD",
"Arity": -3,
"Flags": "MovableKeys, ReadOnly",
"AclCategories": "Read, SortedSet, Slow",
"KeySpecifications": [
{
"BeginSearch": {
"TypeDiscriminator": "BeginSearchIndex",
"Index": 1
},
"FindKeys": {
"TypeDiscriminator": "FindKeysKeyNum",
"KeyNumIdx": 0,
"FirstKey": 1,
"KeyStep": 1
},
"Flags": "RO, Access"
}
]
},
{
"Command": "ZINTERSTORE",
"Name": "ZINTERSTORE",
"Arity": -4,
"Flags": "DenyOom, MovableKeys, Write",
"FirstKey": 1,
"LastKey": 1,
"Step": 1,
"AclCategories": "SortedSet, Slow, Write",
"KeySpecifications": [
{
"BeginSearch": {
"TypeDiscriminator": "BeginSearchIndex",
"Index": 1
},
"FindKeys": {
"TypeDiscriminator": "FindKeysRange",
"LastKey": 0,
"KeyStep": 1,
"Limit": 0
},
"Flags": "OW, Update"
},
{
"BeginSearch": {
"TypeDiscriminator": "BeginSearchIndex",
"Index": 2
},
"FindKeys": {
"TypeDiscriminator": "FindKeysKeyNum",
"KeyNumIdx": 0,
"FirstKey": 1,
"KeyStep": 1
},
"Flags": "RO, Access"
}
]
},
{
"Command": "ZLEXCOUNT",
"Name": "ZLEXCOUNT",
Expand Down
12 changes: 12 additions & 0 deletions libs/server/API/GarnetApiObjectCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ public GarnetStatus SortedSetDifferenceStore(ArgSlice destinationKey, ReadOnlySp
public GarnetStatus SortedSetScan(ArgSlice key, long cursor, string match, int count, out ArgSlice[] items)
=> storageSession.ObjectScan(GarnetObjectType.SortedSet, key, cursor, match, count, out items, ref objectContext);

/// <inheritdoc />
public GarnetStatus SortedSetIntersect(ReadOnlySpan<ArgSlice> keys, double[] weights, SortedSetAggregateType aggregateType, out Dictionary<byte[], double> pairs)
=> storageSession.SortedSetIntersect(keys, weights, aggregateType, out pairs);

/// <inheritdoc />
public GarnetStatus SortedSetIntersectLength(ReadOnlySpan<ArgSlice> keys, int? limit, out int count)
=> storageSession.SortedSetIntersectLength(keys, limit, out count);

/// <inheritdoc />
public GarnetStatus SortedSetIntersectStore(ArgSlice destinationKey, ReadOnlySpan<ArgSlice> keys, double[] weights, SortedSetAggregateType aggregateType, out int count)
=> storageSession.SortedSetIntersectStore(destinationKey, keys, weights, aggregateType, out count);

#endregion

#region Geospatial commands
Expand Down
20 changes: 20 additions & 0 deletions libs/server/API/GarnetWatchApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,26 @@ public GarnetStatus SortedSetScan(ArgSlice key, long cursor, string match, int c
return garnetApi.SortedSetScan(key, cursor, match, count, out items);
}

/// <inheritdoc />
public GarnetStatus SortedSetIntersect(ReadOnlySpan<ArgSlice> keys, double[] weights, SortedSetAggregateType aggregateType, out Dictionary<byte[], double> pairs)
{
foreach (var key in keys)
{
garnetApi.WATCH(key, StoreType.Object);
}
return garnetApi.SortedSetIntersect(keys, weights, aggregateType, out pairs);
}

/// <inheritdoc />
public GarnetStatus SortedSetIntersectLength(ReadOnlySpan<ArgSlice> keys, int? limit, out int count)
{
foreach (var key in keys)
{
garnetApi.WATCH(key, StoreType.Object);
}
return garnetApi.SortedSetIntersectLength(keys, limit, out count);
}

#endregion

#region List Methods
Expand Down
30 changes: 30 additions & 0 deletions libs/server/API/IGarnetApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,17 @@ public interface IGarnetApi : IGarnetReadApi, IGarnetAdvancedApi
/// <returns></returns>
GarnetStatus GeoSearchStore(ArgSlice key, ArgSlice destinationKey, ref ObjectInput input, ref SpanByteAndMemory output);

/// <summary>
/// Intersects multiple sorted sets and stores the result in the destination key.
/// </summary>
/// <param name="destinationKey">The key where the result will be stored.</param>
/// <param name="keys">The keys of the sorted sets to intersect.</param>
/// <param name="weights">The weights to apply to each sorted set during the intersection.</param>
/// <param name="aggregateType">The type of aggregation to use for the intersection.</param>
/// <param name="count">The number of elements in the resulting sorted set.</param>
/// <returns>A <see cref="GarnetStatus"/> indicating the status of the operation.</returns>
GarnetStatus SortedSetIntersectStore(ArgSlice destinationKey, ReadOnlySpan<ArgSlice> keys, double[] weights, SortedSetAggregateType aggregateType, out int count);

#endregion

#region Set Methods
Expand Down Expand Up @@ -1295,6 +1306,25 @@ public interface IGarnetReadApi
/// <returns></returns>
GarnetStatus SortedSetScan(ArgSlice key, long cursor, string match, int count, out ArgSlice[] items);

/// <summary>
/// Intersects multiple sorted sets and returns the result.
/// </summary>
/// <param name="keys">The keys of the sorted sets to intersect.</param>
/// <param name="weights">The weights to apply to each sorted set.</param>
/// <param name="aggregateType">The type of aggregation to perform.</param>
/// <param name="pairs">The resulting dictionary of intersected elements and their scores.</param>
/// <returns>A <see cref="GarnetStatus"/> indicating the status of the operation.</returns>
GarnetStatus SortedSetIntersect(ReadOnlySpan<ArgSlice> keys, double[] weights, SortedSetAggregateType aggregateType, out Dictionary<byte[], double> pairs);

/// <summary>
/// Computes the intersection of multiple sorted sets and counts the elements.
/// </summary>
/// <param name="keys">Input sorted set keys</param>
/// <param name="limit">Optional max count limit</param>
/// <param name="count">The count of elements in the intersection</param>
/// <returns>Operation status</returns>
GarnetStatus SortedSetIntersectLength(ReadOnlySpan<ArgSlice> keys, int? limit, out int count);

#endregion

#region Geospatial Methods
Expand Down
7 changes: 6 additions & 1 deletion libs/server/Resp/CmdStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,12 @@ static partial class CmdStrings
public static ReadOnlySpan<byte> LEFT => "LEFT"u8;
public static ReadOnlySpan<byte> BYLEX => "BYLEX"u8;
public static ReadOnlySpan<byte> REV => "REV"u8;
public static ReadOnlySpan<byte> LIMIT => "LIMIT"u8;
public static ReadOnlySpan<byte> WEIGHTS => "WEIGHTS"u8;
public static ReadOnlySpan<byte> AGGREGATE => "AGGREGATE"u8;
public static ReadOnlySpan<byte> SUM => "SUM"u8;
public static ReadOnlySpan<byte> MIN => "MIN"u8;
public static ReadOnlySpan<byte> MAX => "MAX"u8;
public static ReadOnlySpan<byte> LIMIT => "LIMIT"u8;

/// <summary>
/// Response strings
Expand Down Expand Up @@ -224,7 +227,9 @@ static partial class CmdStrings
"ERR Invalid number of parameters to stored proc {0}, expected {1}, actual {2}";
public const string GenericSyntaxErrorOption = "ERR Syntax error in {0} option '{1}'";
public const string GenericParamShouldBeGreaterThanZero = "ERR {0} should be greater than 0";
public const string GenericErrNotAFloat = "ERR {0} value is not a valid float";
public const string GenericErrCantBeNegative = "ERR {0} can't be negative";
public const string GenericErrAtLeastOneKey = "ERR at least 1 input key is needed for '{0}' command";
public const string GenericErrShouldBeGreaterThanZero = "ERR {0} should be greater than 0";
public const string GenericUnknownClientType = "ERR Unknown client type '{0}'";
public const string GenericErrDuplicateFilter = "ERR Filter '{0}' defined multiple times";
Expand Down
Loading

0 comments on commit f1f9e51

Please sign in to comment.