Skip to content

Commit 3ac77e8

Browse files
authored
Refactoring TxnKeyManager to use Key Specifications (#1381)
* wip * wip * wip * wip * wip * wip * Updating info + docs jsons * Simplifying TKM * wip * test fix * format * bugfixes * some fixes * wip * tests fix * format * small rename
1 parent 61c206f commit 3ac77e8

18 files changed

+2473
-1412
lines changed

libs/resources/RespCommandsDocs.json

Lines changed: 139 additions & 77 deletions
Large diffs are not rendered by default.

libs/resources/RespCommandsInfo.json

Lines changed: 430 additions & 252 deletions
Large diffs are not rendered by default.

libs/server/Cluster/StoreType.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ namespace Garnet.server
1010
/// </summary>
1111
public enum StoreType : byte
1212
{
13+
/// <summary>
14+
/// No store specified
15+
/// </summary>
16+
None = 0,
17+
1318
/// <summary>
1419
/// Main (raw string) store
1520
/// </summary>

libs/server/Resp/BasicCommands.cs

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,23 +1175,21 @@ private bool NetworkCOMMAND_GETKEYS()
11751175
}
11761176

11771177
var cmdName = parseState.GetString(0);
1178-
bool cmdFound = RespCommandsInfo.TryGetRespCommandInfo(cmdName, out var cmdInfo, true, true, logger) ||
1179-
storeWrapper.customCommandManager.TryGetCustomCommandInfo(cmdName, out cmdInfo);
11801178

1181-
if (!cmdFound)
1182-
{
1179+
// Try to parse command and get its simplified info
1180+
if (!TryGetSimpleCommandInfo(cmdName, out var simpleCmdInfo))
11831181
return AbortWithErrorMessage(CmdStrings.RESP_INVALID_COMMAND_SPECIFIED);
1184-
}
11851182

1186-
if (cmdInfo.KeySpecifications == null || cmdInfo.KeySpecifications.Length == 0)
1187-
{
1183+
// If command has no key specifications, abort with error
1184+
if (simpleCmdInfo.KeySpecs == null || simpleCmdInfo.KeySpecs.Length == 0)
11881185
return AbortWithErrorMessage(CmdStrings.RESP_COMMAND_HAS_NO_KEY_ARGS);
1189-
}
1190-
1191-
parseState.TryExtractKeysFromSpecs(cmdInfo.KeySpecifications, out var keys);
11921186

1187+
// Extract command keys from parse state and key specification
1188+
// An offset is applied to the parse state, as the command (and possibly subcommand) are included in the parse state.
1189+
var slicedParseState = parseState.Slice(simpleCmdInfo.IsSubCommand ? 2 : 1);
1190+
var keys = slicedParseState.ExtractCommandKeys(simpleCmdInfo);
11931191

1194-
while (!RespWriteUtils.TryWriteArrayLength(keys.Count, ref dcurr, dend))
1192+
while (!RespWriteUtils.TryWriteArrayLength(keys.Length, ref dcurr, dend))
11951193
SendAndReset();
11961194

11971195
foreach (var key in keys)
@@ -1214,35 +1212,35 @@ private bool NetworkCOMMAND_GETKEYSANDFLAGS()
12141212
}
12151213

12161214
var cmdName = parseState.GetString(0);
1217-
bool cmdFound = RespCommandsInfo.TryGetRespCommandInfo(cmdName, out var cmdInfo, true, true, logger) ||
1218-
storeWrapper.customCommandManager.TryGetCustomCommandInfo(cmdName, out cmdInfo);
12191215

1220-
if (!cmdFound)
1221-
{
1216+
// Try to parse command and get its simplified info
1217+
if (!TryGetSimpleCommandInfo(cmdName, out var simpleCmdInfo))
12221218
return AbortWithErrorMessage(CmdStrings.RESP_INVALID_COMMAND_SPECIFIED);
1223-
}
12241219

1225-
if (cmdInfo.KeySpecifications == null || cmdInfo.KeySpecifications.Length == 0)
1226-
{
1220+
// If command has no key specifications, abort with error
1221+
if (simpleCmdInfo.KeySpecs == null || simpleCmdInfo.KeySpecs.Length == 0)
12271222
return AbortWithErrorMessage(CmdStrings.RESP_COMMAND_HAS_NO_KEY_ARGS);
1228-
}
12291223

1230-
parseState.TryExtractKeysAndFlagsFromSpecs(cmdInfo.KeySpecifications, out var keys, out var flags);
1224+
// Extract command keys from parse state and key specification
1225+
// An offset is applied to the parse state, as the command (and possibly subcommand) are included in the parse state.
1226+
var slicedParseState = parseState.Slice(simpleCmdInfo.IsSubCommand ? 2 : 1);
1227+
var keysAndFlags = slicedParseState.ExtractCommandKeysAndFlags(simpleCmdInfo);
12311228

1232-
while (!RespWriteUtils.TryWriteArrayLength(keys.Count, ref dcurr, dend))
1229+
while (!RespWriteUtils.TryWriteArrayLength(keysAndFlags.Length, ref dcurr, dend))
12331230
SendAndReset();
12341231

1235-
for (int i = 0; i < keys.Count; i++)
1232+
for (var i = 0; i < keysAndFlags.Length; i++)
12361233
{
12371234
while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend))
12381235
SendAndReset();
12391236

1240-
while (!RespWriteUtils.TryWriteBulkString(keys[i].Span, ref dcurr, dend))
1237+
while (!RespWriteUtils.TryWriteBulkString(keysAndFlags[i].Item1.Span, ref dcurr, dend))
12411238
SendAndReset();
12421239

1243-
WriteSetLength(flags[i].Length);
1240+
var flags = EnumUtils.GetEnumDescriptions(keysAndFlags[i].Item2);
1241+
WriteSetLength(flags.Length);
12441242

1245-
foreach (var flag in flags[i])
1243+
foreach (var flag in flags)
12461244
{
12471245
while (!RespWriteUtils.TryWriteBulkString(Encoding.ASCII.GetBytes(flag), ref dcurr, dend))
12481246
SendAndReset();
@@ -1762,6 +1760,29 @@ bool ParseGETAndKey(ref SpanByte key)
17621760
return true;
17631761
}
17641762

1763+
private bool TryGetSimpleCommandInfo(string cmdName, out SimpleRespCommandInfo simpleCmdInfo)
1764+
{
1765+
simpleCmdInfo = SimpleRespCommandInfo.Default;
1766+
1767+
// Try to parse known command from name and obtain its command info
1768+
if (!Enum.TryParse<RespCommand>(cmdName, true, out var cmd) ||
1769+
!RespCommandsInfo.TryGetSimpleRespCommandInfo(cmd, out simpleCmdInfo, logger))
1770+
{
1771+
// If we no known command or info was found, attempt to find custom command
1772+
if (storeWrapper.customCommandManager.TryGetCustomCommandInfo(cmdName, out var cmdInfo))
1773+
{
1774+
cmdInfo.PopulateSimpleCommandInfo(ref simpleCmdInfo);
1775+
}
1776+
else
1777+
{
1778+
// No matching command was found
1779+
return false;
1780+
}
1781+
}
1782+
1783+
return true;
1784+
}
1785+
17651786
static void SetResult(int c, ref int firstPending, ref (GarnetStatus, SpanByteAndMemory)[] outputArr,
17661787
GarnetStatus status, SpanByteAndMemory output)
17671788
{
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Runtime.InteropServices;
8+
using System.Text;
9+
10+
namespace Garnet.server
11+
{
12+
/// <summary>
13+
/// Represents a simplified version of RESP command's information
14+
/// </summary>
15+
public struct SimpleRespCommandInfo
16+
{
17+
/// <summary>
18+
/// Command Arity
19+
/// </summary>
20+
public sbyte Arity;
21+
22+
/// <summary>
23+
/// True if command is allowed in a transaction context
24+
/// </summary>
25+
public bool AllowedInTxn;
26+
27+
/// <summary>
28+
/// True if command has sub-commands
29+
/// </summary>
30+
public bool IsParent;
31+
32+
/// <summary>
33+
/// True if command is a sub-command
34+
/// </summary>
35+
public bool IsSubCommand;
36+
37+
/// <summary>
38+
/// Simplified command key specifications
39+
/// </summary>
40+
public SimpleRespKeySpec[] KeySpecs;
41+
42+
/// <summary>
43+
/// Store type that the command operates on (None/Main/Object/All). Default: None for commands without key arguments.
44+
/// </summary>
45+
public StoreType StoreType;
46+
47+
/// <summary>
48+
/// Default SimpleRespCommandInfo
49+
/// </summary>
50+
public static SimpleRespCommandInfo Default = new();
51+
}
52+
53+
/// <summary>
54+
/// Represents a simplified version of a single key specification of a RESP command
55+
/// </summary>
56+
public struct SimpleRespKeySpecBeginSearch
57+
{
58+
/// <summary>
59+
/// Keyword that precedes the keys in the command arguments
60+
/// </summary>
61+
public byte[] Keyword;
62+
63+
/// <summary>
64+
/// Index of first key or the index at which to start searching for keyword (if begin search is of keyword type)
65+
/// </summary>
66+
public int Index;
67+
68+
/// <summary>
69+
/// If true - begin search is of type index, otherwise begin search is of type keyword
70+
/// </summary>
71+
public bool IsIndexType;
72+
73+
/// <summary>
74+
/// Set begin search of type index
75+
/// </summary>
76+
/// <param name="index">Index of first key</param>
77+
public SimpleRespKeySpecBeginSearch(int index)
78+
{
79+
IsIndexType = true;
80+
Index = index;
81+
}
82+
83+
/// <summary>
84+
/// Set begin search of type keyword
85+
/// </summary>
86+
/// <param name="keyword">Keyword that precedes the keys in the command arguments</param>
87+
/// <param name="startIdx">Index at which to start searching for keyword</param>
88+
public SimpleRespKeySpecBeginSearch(string keyword, int startIdx)
89+
{
90+
Index = startIdx;
91+
Keyword = Encoding.UTF8.GetBytes(keyword);
92+
}
93+
}
94+
95+
/// <summary>
96+
/// Represents a simplified version of a single key specification of a RESP command
97+
/// </summary>
98+
[StructLayout(LayoutKind.Explicit, Size = Size)]
99+
public struct SimpleRespKeySpecFindKeys
100+
{
101+
/// <summary>
102+
/// Size of struct
103+
/// </summary>
104+
public const int Size = 10;
105+
106+
/// <summary>
107+
/// The index (relative to begin search) of the argument containing the number of keys
108+
/// </summary>
109+
[FieldOffset(0)]
110+
public int KeyNumIndex;
111+
112+
/// <summary>
113+
/// the index (relative to begin search) of the first key
114+
/// </summary>
115+
[FieldOffset(4)]
116+
public int FirstKey;
117+
118+
/// <summary>
119+
/// The index (relative to begin search) of the last key argument or limit - stops the key search by a factor.
120+
/// 0 and 1 mean no limit. 2 means half of the remaining arguments, 3 means a third, and so on.
121+
/// Limit is used if IsRangeLimitType is set.
122+
/// </summary>
123+
[FieldOffset(0)]
124+
public int LastKeyOrLimit;
125+
126+
/// <summary>
127+
/// The number of arguments that should be skipped, after finding a key, to find the next one.
128+
/// </summary>
129+
[FieldOffset(4)]
130+
public int KeyStep;
131+
132+
/// <summary>
133+
/// If true - find keys is of type range, otherwise find keys is of type keynum
134+
/// </summary>
135+
[FieldOffset(8)]
136+
public bool IsRangeType;
137+
138+
/// <summary>
139+
/// If true - find keys is of type range and limit is used, otherwise find keys is of type range and last key is used.
140+
/// </summary>
141+
[FieldOffset(9)]
142+
public bool IsRangeLimitType;
143+
144+
/// <summary>
145+
/// Set find keys of type range
146+
/// </summary>
147+
/// <param name="keyStep">The number of arguments that should be skipped, after finding a key, to find the next one</param>
148+
/// <param name="lastKeyOrLimit">The index of the last key argument or the limit</param>
149+
/// <param name="isLimit">If preceding argument represents a limit</param>
150+
public SimpleRespKeySpecFindKeys(int keyStep, int lastKeyOrLimit, bool isLimit)
151+
{
152+
IsRangeType = true;
153+
KeyStep = keyStep;
154+
LastKeyOrLimit = lastKeyOrLimit;
155+
IsRangeLimitType = isLimit;
156+
}
157+
158+
/// <summary>
159+
/// Set find keys of type keynum
160+
/// </summary>
161+
/// <param name="keyNumIndex">The index of the argument containing the number of keys</param>
162+
/// <param name="firstKey">The index of the first key</param>
163+
/// <param name="keyStep">The number of arguments that should be skipped, after finding a key, to find the next one</param>
164+
public SimpleRespKeySpecFindKeys(int keyNumIndex, int firstKey, int keyStep)
165+
{
166+
KeyNumIndex = keyNumIndex;
167+
FirstKey = firstKey;
168+
KeyStep = keyStep;
169+
}
170+
}
171+
172+
/// <summary>
173+
/// Represents a simplified version of a single key specification of a RESP command
174+
/// </summary>
175+
public struct SimpleRespKeySpec
176+
{
177+
/// <summary>
178+
/// Begin search specification
179+
/// </summary>
180+
public SimpleRespKeySpecBeginSearch BeginSearch;
181+
182+
/// <summary>
183+
/// Find keys specification
184+
/// </summary>
185+
public SimpleRespKeySpecFindKeys FindKeys;
186+
187+
/// <summary>
188+
/// Key specification flags
189+
/// </summary>
190+
public KeySpecificationFlags Flags;
191+
}
192+
193+
/// <summary>
194+
/// Extension methods for obtaining simplified RESP command info structs
195+
/// </summary>
196+
public static class RespCommandInfoExtensions
197+
{
198+
/// <summary>
199+
/// Populates a SimpleRespCommandInfo struct from a RespCommandsInfo instance
200+
/// </summary>
201+
/// <param name="cmdInfo">The source RespCommandsInfo</param>
202+
/// <param name="simpleCmdInfo">The destination SimpleRespCommandInfo</param>
203+
public static void PopulateSimpleCommandInfo(this RespCommandsInfo cmdInfo, ref SimpleRespCommandInfo simpleCmdInfo)
204+
{
205+
var arity = cmdInfo.Arity;
206+
207+
// Verify that arity is in the signed byte range (-128 to 127)
208+
Debug.Assert(arity is <= sbyte.MaxValue and >= sbyte.MinValue);
209+
210+
simpleCmdInfo.Arity = (sbyte)arity;
211+
simpleCmdInfo.AllowedInTxn = (cmdInfo.Flags & RespCommandFlags.NoMulti) == 0;
212+
simpleCmdInfo.IsParent = (cmdInfo.SubCommands?.Length ?? 0) > 0;
213+
simpleCmdInfo.IsSubCommand = cmdInfo.Parent != null;
214+
simpleCmdInfo.StoreType = cmdInfo.StoreType;
215+
216+
if (cmdInfo.KeySpecifications != null)
217+
{
218+
var tmpSimpleKeySpecs = new List<SimpleRespKeySpec>();
219+
220+
foreach (var keySpec in cmdInfo.KeySpecifications)
221+
{
222+
if (keySpec.TryGetSimpleKeySpec(out var simpleKeySpec))
223+
tmpSimpleKeySpecs.Add(simpleKeySpec);
224+
}
225+
226+
simpleCmdInfo.KeySpecs = tmpSimpleKeySpecs.ToArray();
227+
}
228+
}
229+
230+
/// <summary>
231+
/// Tries to convert a RespCommandKeySpecification to a SimpleRespKeySpec
232+
/// </summary>
233+
/// <param name="keySpec">The source RespCommandKeySpecification</param>
234+
/// <param name="simpleKeySpec">The resulting SimpleRespKeySpec</param>
235+
/// <returns>True if successful</returns>
236+
public static bool TryGetSimpleKeySpec(this RespCommandKeySpecification keySpec, out SimpleRespKeySpec simpleKeySpec)
237+
{
238+
simpleKeySpec = new SimpleRespKeySpec();
239+
240+
if (keySpec.BeginSearch is BeginSearchUnknown || keySpec.FindKeys is FindKeysUnknown)
241+
return false;
242+
243+
simpleKeySpec.BeginSearch = keySpec.BeginSearch switch
244+
{
245+
BeginSearchIndex bsi => new SimpleRespKeySpecBeginSearch(bsi.Index),
246+
BeginSearchKeyword bsk => new SimpleRespKeySpecBeginSearch(bsk.Keyword, bsk.StartFrom),
247+
_ => throw new NotSupportedException()
248+
};
249+
250+
simpleKeySpec.FindKeys = keySpec.FindKeys switch
251+
{
252+
FindKeysRange fkr => fkr.LastKey == -1
253+
? new SimpleRespKeySpecFindKeys(fkr.KeyStep, fkr.Limit, true)
254+
: new SimpleRespKeySpecFindKeys(fkr.KeyStep, fkr.LastKey, false),
255+
FindKeysKeyNum fkk => new SimpleRespKeySpecFindKeys(fkk.KeyNumIdx, fkk.FirstKey, fkk.KeyStep),
256+
_ => throw new NotSupportedException()
257+
};
258+
259+
simpleKeySpec.Flags = keySpec.Flags;
260+
261+
return true;
262+
}
263+
}
264+
}

0 commit comments

Comments
 (0)