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

[Compatibility] Added LCS command #843

Merged
merged 18 commits into from
Jan 7, 2025
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
55 changes: 55 additions & 0 deletions libs/resources/RespCommandsDocs.json
Original file line number Diff line number Diff line change
Expand Up @@ -3337,6 +3337,61 @@
}
]
},
{
"Command": "LCS",
"Name": "LCS",
"Summary": "Finds the longest common substring.",
"Group": "String",
"Complexity": "O(N*M) where N and M are the lengths of s1 and s2, respectively",
"Arguments": [
{
"TypeDiscriminator": "RespCommandKeyArgument",
"Name": "KEY1",
"DisplayText": "key1",
"Type": "Key",
"KeySpecIndex": 0
},
{
"TypeDiscriminator": "RespCommandKeyArgument",
"Name": "KEY2",
"DisplayText": "key2",
"Type": "Key",
"KeySpecIndex": 0
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "LEN",
"DisplayText": "len",
"Type": "PureToken",
"Token": "LEN",
"ArgumentFlags": "Optional"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "IDX",
"DisplayText": "idx",
"Type": "PureToken",
"Token": "IDX",
"ArgumentFlags": "Optional"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "MIN-MATCH-LEN",
"DisplayText": "min-match-len",
"Type": "Integer",
"Token": "MINMATCHLEN",
"ArgumentFlags": "Optional"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "WITHMATCHLEN",
"DisplayText": "withmatchlen",
"Type": "PureToken",
"Token": "WITHMATCHLEN",
"ArgumentFlags": "Optional"
}
]
},
{
"Command": "LINDEX",
"Name": "LINDEX",
Expand Down
25 changes: 25 additions & 0 deletions libs/resources/RespCommandsInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -2113,6 +2113,31 @@
}
]
},
{
"Command": "LCS",
"Name": "LCS",
"Arity": -3,
"Flags": "ReadOnly",
"FirstKey": 1,
"LastKey": 2,
"Step": 1,
"AclCategories": "Read, Slow, String",
"KeySpecifications": [
{
"BeginSearch": {
"TypeDiscriminator": "BeginSearchIndex",
"Index": 1
},
"FindKeys": {
"TypeDiscriminator": "FindKeysRange",
"LastKey": 1,
"KeyStep": 1,
"Limit": 0
},
"Flags": "RO, Access"
}
]
},
{
"Command": "LINDEX",
"Name": "LINDEX",
Expand Down
4 changes: 4 additions & 0 deletions libs/server/API/GarnetApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public unsafe GarnetStatus GET(ArgSlice key, out ArgSlice value)
/// <inheritdoc />
public GarnetStatus GET(byte[] key, out GarnetObjectStoreOutput value)
=> storageSession.GET(key, out value, ref objectContext);

/// <inheritdoc />
public GarnetStatus LCS(ArgSlice key1, ArgSlice key2, ref SpanByteAndMemory output, bool lenOnly = false, bool withIndices = false, bool withMatchLen = false, int minMatchLen = 0)
=> storageSession.LCS(key1, key2, ref output, lenOnly, withIndices, withMatchLen, minMatchLen);
#endregion

#region GETEX
Expand Down
8 changes: 8 additions & 0 deletions libs/server/API/GarnetWatchApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ public GarnetStatus GET(byte[] key, out GarnetObjectStoreOutput value)
garnetApi.WATCH(key, StoreType.Object);
return garnetApi.GET(key, out value);
}

/// <inheritdoc />
public GarnetStatus LCS(ArgSlice key1, ArgSlice key2, ref SpanByteAndMemory output, bool lenOnly = false, bool withIndices = false, bool withMatchLen = false, int minMatchLen = 0)
{
garnetApi.WATCH(key1, StoreType.Object);
garnetApi.WATCH(key2, StoreType.Object);
return garnetApi.LCS(key1, key2, ref output, lenOnly, withIndices, withMatchLen, minMatchLen);
}
#endregion

#region GETRANGE
Expand Down
13 changes: 13 additions & 0 deletions libs/server/API/IGarnetApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,19 @@ public interface IGarnetReadApi
/// <param name="value"></param>
/// <returns></returns>
GarnetStatus GET(byte[] key, out GarnetObjectStoreOutput value);

/// <summary>
/// Finds the longest common subsequence (LCS) between two keys.
/// </summary>
/// <param name="key1">The first key to compare.</param>
/// <param name="key2">The second key to compare.</param>
/// <param name="output">The output containing the LCS result.</param>
/// <param name="lenOnly">If true, only the length of the LCS is returned.</param>
/// <param name="withIndices">If true, the indices of the LCS in both keys are returned.</param>
/// <param name="withMatchLen">If true, the length of each match is returned.</param>
/// <param name="minMatchLen">The minimum length of a match to be considered.</param>
/// <returns>The status of the operation.</returns>
GarnetStatus LCS(ArgSlice key1, ArgSlice key2, ref SpanByteAndMemory output, bool lenOnly = false, bool withIndices = false, bool withMatchLen = false, int minMatchLen = 0);
#endregion

#region GETRANGE
Expand Down
72 changes: 72 additions & 0 deletions libs/server/Resp/ArrayCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,5 +450,77 @@ private bool NetworkArrayPING()
WriteDirectLargeRespString(message);
return true;
}

private bool NetworkLCS<TGarnetApi>(ref TGarnetApi storageApi)
where TGarnetApi : IGarnetApi
{
if (parseState.Count < 2)
return AbortWithWrongNumberOfArguments(nameof(RespCommand.LCS));

var key1 = parseState.GetArgSliceByRef(0);
var key2 = parseState.GetArgSliceByRef(1);

// Parse options
var lenOnly = false;
var withIndices = false;
var minMatchLen = 0;
var withMatchLen = false;
var tokenIdx = 2;
while (tokenIdx < parseState.Count)
{
var option = parseState.GetArgSliceByRef(tokenIdx++).ReadOnlySpan;

if (option.EqualsUpperCaseSpanIgnoringCase(CmdStrings.LEN))
{
lenOnly = true;
}
else if (option.EqualsUpperCaseSpanIgnoringCase(CmdStrings.IDX))
{
withIndices = true;
}
else if (option.EqualsUpperCaseSpanIgnoringCase(CmdStrings.MINMATCHLEN))
{
if (tokenIdx + 1 > parseState.Count)
{
return AbortWithErrorMessage(CmdStrings.RESP_SYNTAX_ERROR);
}

if (!parseState.TryGetInt(tokenIdx++, out var minLen))
{
return AbortWithErrorMessage(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER);
}

if (minLen < 0)
{
minLen = 0;
}

minMatchLen = minLen;
}
else if (option.EqualsUpperCaseSpanIgnoringCase(CmdStrings.WITHMATCHLEN))
{
withMatchLen = true;
}
else
{
return AbortWithErrorMessage(CmdStrings.RESP_SYNTAX_ERROR);
}
}

if (lenOnly && withIndices)
{
return AbortWithErrorMessage(CmdStrings.RESP_ERR_LENGTH_AND_INDEXES);
}

var output = new SpanByteAndMemory(dcurr, (int)(dend - dcurr));
var status = storageApi.LCS(key1, key2, ref output, lenOnly, withIndices, withMatchLen, minMatchLen);

if (!output.IsSpanByte)
SendAndReset(output.Memory, output.Length);
else
dcurr += output.Length;

return true;
}
}
}
7 changes: 7 additions & 0 deletions libs/server/Resp/CmdStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ static partial class CmdStrings
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> LEN => "LEN"u8;
public static ReadOnlySpan<byte> IDX => "IDX"u8;
public static ReadOnlySpan<byte> MINMATCHLEN => "MINMATCHLEN"u8;
public static ReadOnlySpan<byte> WITHMATCHLEN => "WITHMATCHLEN"u8;

/// <summary>
/// Response strings
Expand All @@ -140,6 +144,8 @@ static partial class CmdStrings
public static ReadOnlySpan<byte> RESP_PONG => "+PONG\r\n"u8;
public static ReadOnlySpan<byte> RESP_EMPTY => "$0\r\n\r\n"u8;
public static ReadOnlySpan<byte> RESP_QUEUED => "+QUEUED\r\n"u8;
public static ReadOnlySpan<byte> matches => "matches"u8;
public static ReadOnlySpan<byte> len => "len"u8;

/// <summary>
/// Simple error response strings, i.e. these are of the form "-errorString\r\n"
Expand Down Expand Up @@ -215,6 +221,7 @@ static partial class CmdStrings
public static ReadOnlySpan<byte> RESP_ERR_INCR_SUPPORTS_ONLY_SINGLE_PAIR => "ERR INCR option supports a single increment-element pair"u8;
public static ReadOnlySpan<byte> RESP_ERR_INVALID_BITFIELD_TYPE => "ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is"u8;
public static ReadOnlySpan<byte> RESP_ERR_SCRIPT_FLUSH_OPTIONS => "ERR SCRIPT FLUSH only support SYNC|ASYNC option"u8;
public static ReadOnlySpan<byte> RESP_ERR_LENGTH_AND_INDEXES => "If you want both the length and indexes, please just use IDX."u8;

/// <summary>
/// Response string templates
Expand Down
5 changes: 5 additions & 0 deletions libs/server/Resp/Parser/RespCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public enum RespCommand : ushort
HSTRLEN,
HVALS,
KEYS,
LCS,
LINDEX,
LLEN,
LPOS,
Expand Down Expand Up @@ -772,6 +773,10 @@ private RespCommand FastParseArrayCommand(ref int count, ref ReadOnlySpan<byte>
{
return RespCommand.DEL;
}
else if (*(ulong*)(ptr + 1) == MemoryMarshal.Read<ulong>("3\r\nLCS\r\n"u8))
{
return RespCommand.LCS;
}

break;

Expand Down
2 changes: 2 additions & 0 deletions libs/server/Resp/RespServerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,8 @@ private bool ProcessOtherCommands<TGarnetApi>(RespCommand command, ref TGarnetAp

RespCommand.EVAL => TryEVAL(),
RespCommand.EVALSHA => TryEVALSHA(),
// Slow commands
RespCommand.LCS => NetworkLCS(ref storageApi),
_ => Process(command)
};

Expand Down
Loading
Loading