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

Add SETIFGREATER ETag command #968

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
64 changes: 64 additions & 0 deletions libs/resources/RespCommandsDocs.json
Original file line number Diff line number Diff line change
Expand Up @@ -6160,6 +6160,63 @@
}
]
},
{
"Command": "SETIFGREATER",
"Name": "SETIFGREATER",
"Summary": "Sets a key value pair using the given etag incremented only if (1) the etag given in the request is greater than the already existing etag ; or (2) the existing value was not associated with any etag and the given etag is more than 0.",
"Group": "String",
"Complexity": "O(1)",
"Arguments": [
{
"TypeDiscriminator": "RespCommandKeyArgument",
"Name": "KEY",
"DisplayText": "key",
"Type": "Key",
"KeySpecIndex": 0
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "VALUE",
"DisplayText": "value",
"Type": "String"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "ETAG",
"DisplayText": "etag",
"Type": "Integer"
},
{
"TypeDiscriminator": "RespCommandContainerArgument",
"Name": "EXPIRATION",
"Type": "OneOf",
"ArgumentFlags": "Optional",
"Arguments": [
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "SECONDS",
"DisplayText": "seconds",
"Type": "Integer",
"Token": "EX"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "MILLISECONDS",
"DisplayText": "milliseconds",
"Type": "Integer",
"Token": "PX"
}
]
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "NOGET",
"DisplayText": "noget",
"Type": "PureToken",
"Token": "NOGET"
}
]
},
{
"Command": "SETIFMATCH",
"Name": "SETIFMATCH",
Expand Down Expand Up @@ -6207,6 +6264,13 @@
"Token": "PX"
}
]
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "NOGET",
"DisplayText": "noget",
"Type": "PureToken",
"Token": "NOGET"
}
]
},
Expand Down
26 changes: 25 additions & 1 deletion libs/resources/RespCommandsInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -1724,7 +1724,7 @@
"FirstKey": 1,
"LastKey": 1,
"Step": 1,
"AclCategories": "Hash, Write, Admin, Garnet",
"AclCategories": "Admin, Hash, Write, Garnet",
"KeySpecifications": [
{
"BeginSearch": {
Expand Down Expand Up @@ -3921,6 +3921,30 @@
}
]
},
{
"Command": "SETIFGREATER",
"Name": "SETIFGREATER",
"Arity": -4,
"FirstKey": 1,
"LastKey": 1,
"Step": 1,
"AclCategories": "Slow, String, Write",
"KeySpecifications": [
{
"BeginSearch": {
"TypeDiscriminator": "BeginSearchIndex",
"Index": 1
},
"FindKeys": {
"TypeDiscriminator": "FindKeysRange",
"LastKey": 0,
"KeyStep": 1,
"Limit": 0
},
"Flags": "RW, Access, Update, VariableFlags"
}
]
},
{
"Command": "SETIFMATCH",
"Name": "SETIFMATCH",
Expand Down
4 changes: 2 additions & 2 deletions libs/server/Resp/BasicCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,8 @@ private bool NetworkSET_Conditional<TGarnetApi>(RespCommand cmd, int expiry, ref

if (!getValue && !withEtag)
{
// the following debug assertion is the catch any edge case leading to SETIFMATCH skipping the above block
Debug.Assert(cmd != RespCommand.SETIFMATCH, "SETIFMATCH should have gone though pointing to right output variable");
// the following debug assertion is the catch any edge case leading to SETIFMATCH, or SETIFGREATER skipping the above block
Debug.Assert(cmd is not (RespCommand.SETIFMATCH or RespCommand.SETIFGREATER), "SETIFMATCH should have gone though pointing to right output variable");

GarnetStatus status = storageApi.SET_Conditional(ref key, ref input);

Expand Down
77 changes: 68 additions & 9 deletions libs/server/Resp/BasicEtagCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private bool NetworkGETIFNOTMATCH<TGarnetApi>(ref TGarnetApi storageApi)
}

/// <summary>
/// SETIFMATCH key val etag [EX|PX] [expiry]
/// SETIFMATCH key val etag [EX|PX] [expiry] [NOGET]
/// Sets a key value pair only if the already existing etag matches the etag sent as a part of the request.
/// </summary>
/// <typeparam name="TGarnetApi"></typeparam>
Expand All @@ -87,27 +87,86 @@ private bool NetworkGETIFNOTMATCH<TGarnetApi>(ref TGarnetApi storageApi)
private bool NetworkSETIFMATCH<TGarnetApi>(ref TGarnetApi storageApi)
where TGarnetApi : IGarnetApi
{
if (parseState.Count < 3 || parseState.Count > 5)
return NetworkSEtETagConditional(RespCommand.SETIFMATCH, ref storageApi);
}

/// <summary>
/// SETIFGREATER key val etag [EX|PX] [expiry] [NOGET]
/// Sets a key value pair using the given etag incremented only if (1) the etag given in the request is greater than the already existing etag ;
/// or (2) the existing value was not associated with any etag and the given etag is more than 0
/// </summary>
/// <typeparam name="TGarnetApi"></typeparam>
/// <param name="storageApi"></param>
/// <returns></returns>
private bool NetworkSETIFGREATER<TGarnetApi>(ref TGarnetApi storageApi)
hamdaankhalid marked this conversation as resolved.
Show resolved Hide resolved
where TGarnetApi : IGarnetApi
{
return NetworkSEtETagConditional(RespCommand.SETIFGREATER, ref storageApi);
}

private bool NetworkSEtETagConditional<TGarnetApi>(RespCommand cmd, ref TGarnetApi storageApi)
where TGarnetApi : IGarnetApi
{
// Currently only supports these two commands
Debug.Assert(cmd is RespCommand.SETIFMATCH or RespCommand.SETIFGREATER);

if (parseState.Count < 3 || parseState.Count > 6)
{
return AbortWithWrongNumberOfArguments(nameof(RespCommand.SETIFMATCH));
return AbortWithWrongNumberOfArguments(nameof(cmd));
}

int expiry = 0;
ReadOnlySpan<byte> errorMessage = default;
var tokenIdx = 3;

ExpirationOption expOption = ExpirationOption.None;
if (tokenIdx < parseState.Count)
bool noGet = false;

while (tokenIdx < parseState.Count)
{
if (!parseState.TryGetExpirationOption(tokenIdx++, out expOption) || (expOption is not ExpirationOption.EX and not ExpirationOption.PX))
errorMessage = CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR;
else
// Parse NOGET option
if (parseState.GetArgSliceByRef(tokenIdx).Span.EqualsUpperCaseSpanIgnoringCase(CmdStrings.NOGET))
{
if (!parseState.TryGetInt(tokenIdx++, out expiry))
if (noGet)
{
errorMessage = CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR;
break;
}

noGet = true;
tokenIdx++;
continue;
}

// Parse EX | PX expiry combination
if (parseState.TryGetExpirationOption(tokenIdx, out expOption))
{
if (expOption is not ExpirationOption.EX and not ExpirationOption.PX)
{
errorMessage = CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR;
break;
}

// we know that the token is either EX or PX from above and the next value should be the expiry
tokenIdx++;
if (!parseState.TryGetInt(tokenIdx, out expiry))
{
errorMessage = CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER;
break;
}
else if (expiry <= 0)
{
errorMessage = CmdStrings.RESP_ERR_GENERIC_INVALIDEXP_IN_SET;
break;
}

tokenIdx++;
continue;
}

// neither NOGET nor EX|PX expiry combination
errorMessage = CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR;
break;
}

if (!errorMessage.IsEmpty)
Expand All @@ -119,7 +178,7 @@ private bool NetworkSETIFMATCH<TGarnetApi>(ref TGarnetApi storageApi)

SpanByte key = parseState.GetArgSliceByRef(0).SpanByte;

NetworkSET_Conditional(RespCommand.SETIFMATCH, expiry, ref key, getValue: true, highPrecision: expOption == ExpirationOption.PX, withEtag: true, ref storageApi);
NetworkSET_Conditional(cmd, expiry, ref key, getValue: !noGet, highPrecision: expOption == ExpirationOption.PX, withEtag: true, ref storageApi);

return true;
}
Expand Down
2 changes: 2 additions & 0 deletions libs/server/Resp/CmdStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,11 @@ static partial class CmdStrings
public static ReadOnlySpan<byte> GETWITHETAG => "GETWITHETAG"u8;
public static ReadOnlySpan<byte> GETIFNOTMATCH => "GETIFNOTMATCH"u8;
public static ReadOnlySpan<byte> SETIFMATCH => "SETIFMATCH"u8;
public static ReadOnlySpan<byte> SETIFGREATER => "SETIFGREATER"u8;
public static ReadOnlySpan<byte> FIELDS => "FIELDS"u8;
public static ReadOnlySpan<byte> TIMEOUT => "TIMEOUT"u8;
public static ReadOnlySpan<byte> ERROR => "ERROR"u8;
public static ReadOnlySpan<byte> NOGET => "NOGET"u8;

/// <summary>
/// Response strings
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 @@ -171,6 +171,7 @@ public enum RespCommand : ushort
SETEXXX,
SETNX,
SETIFMATCH,
SETIFGREATER,
SETKEEPTTL,
SETKEEPTTLXX,
SETRANGE,
Expand Down Expand Up @@ -2239,6 +2240,10 @@ private RespCommand SlowParseCommand(ref int count, ref ReadOnlySpan<byte> speci
{
return RespCommand.SETIFMATCH;
}
else if (command.SequenceEqual(CmdStrings.SETIFGREATER))
{
return RespCommand.SETIFGREATER;
}
else if (command.SequenceEqual(CmdStrings.GETWITHETAG))
{
return RespCommand.GETWITHETAG;
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 @@ -806,6 +806,7 @@ private bool ProcessOtherCommands<TGarnetApi>(RespCommand command, ref TGarnetAp
RespCommand.GETWITHETAG => NetworkGETWITHETAG(ref storageApi),
RespCommand.GETIFNOTMATCH => NetworkGETIFNOTMATCH(ref storageApi),
RespCommand.SETIFMATCH => NetworkSETIFMATCH(ref storageApi),
RespCommand.SETIFGREATER => NetworkSETIFGREATER(ref storageApi),

_ => Process(command, ref storageApi)
};
Expand Down
2 changes: 1 addition & 1 deletion libs/server/Storage/Functions/EtagState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public EtagState()
/// <summary>
/// Field provides access to getting an Etag from a record, hiding whether it is actually present or not.
/// </summary>
public long etag { get; private set; } = EtagConstants.BaseEtag;
public long etag { get; set; } = EtagConstants.BaseEtag;

/// <summary>
/// Sets the values to indicate the presence of an Etag as a part of the payload value
Expand Down
Loading