Skip to content

Commit

Permalink
[Compatibility] Adding EXPIRETIME and PEXPIRETIME command (#664)
Browse files Browse the repository at this point in the history
* Adding EXPIRETIME and EXPIRETIME command

* Review comment fix for RespInputHeader

* Review comment fix, added switch

---------

Co-authored-by: Tal Zaccai <[email protected]>
  • Loading branch information
Vijay-Nirmal and TalZaccai authored Oct 4, 2024
1 parent 7335d93 commit 41d77ba
Show file tree
Hide file tree
Showing 17 changed files with 535 additions and 19 deletions.
20 changes: 20 additions & 0 deletions libs/common/ConvertUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,25 @@ public static long UnixTimestampInMillisecondsToTicks(long unixTimestamp)
{
return unixTimestamp * TimeSpan.TicksPerMillisecond + _unixEpochTicks;
}

/// <summary>
/// Convert ticks to Unix time in seconds.
/// </summary>
/// <param name="ticks">The ticks to convert.</param>
/// <returns>The Unix time in seconds.</returns>
public static long UnixTimeInSecondsFromTicks(long ticks)
{
return ticks > 0 ? (ticks - _unixEpochTicks) / TimeSpan.TicksPerSecond : -1;
}

/// <summary>
/// Convert ticks to Unix time in milliseconds.
/// </summary>
/// <param name="ticks">The ticks to convert.</param>
/// <returns>The Unix time in milliseconds.</returns>
public static long UnixTimeInMillisecondsFromTicks(long ticks)
{
return ticks > 0 ? (ticks - _unixEpochTicks) / TimeSpan.TicksPerMillisecond : -1;
}
}
}
12 changes: 12 additions & 0 deletions libs/server/API/GarnetApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ public GarnetStatus PTTL(ref SpanByte key, StoreType storeType, ref SpanByteAndM

#endregion

#region EXPIRETIME

/// <inheritdoc />
public GarnetStatus EXPIRETIME(ref SpanByte key, StoreType storeType, ref SpanByteAndMemory output)
=> storageSession.EXPIRETIME(ref key, storeType, ref output, ref context, ref objectContext);

/// <inheritdoc />
public GarnetStatus PEXPIRETIME(ref SpanByte key, StoreType storeType, ref SpanByteAndMemory output)
=> storageSession.EXPIRETIME(ref key, storeType, ref output, ref context, ref objectContext, milliseconds: true);

#endregion

#region SET
/// <inheritdoc />
public GarnetStatus SET(ref SpanByte key, ref SpanByte value)
Expand Down
18 changes: 18 additions & 0 deletions libs/server/API/GarnetWatchApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ public GarnetStatus PTTL(ref SpanByte key, StoreType storeType, ref SpanByteAndM

#endregion

#region EXPIRETIME

/// <inheritdoc />
public GarnetStatus EXPIRETIME(ref SpanByte key, StoreType storeType, ref SpanByteAndMemory output)
{
garnetApi.WATCH(new ArgSlice(ref key), storeType);
return garnetApi.EXPIRETIME(ref key, storeType, ref output);
}

/// <inheritdoc />
public GarnetStatus PEXPIRETIME(ref SpanByte key, StoreType storeType, ref SpanByteAndMemory output)
{
garnetApi.WATCH(new ArgSlice(ref key), storeType);
return garnetApi.PEXPIRETIME(ref key, storeType, ref output);
}

#endregion

#region SortedSet Methods

/// <inheritdoc />
Expand Down
22 changes: 22 additions & 0 deletions libs/server/API/IGarnetApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,28 @@ public interface IGarnetReadApi

#endregion

#region EXPIRETIME

/// <summary>
/// Returns the absolute Unix timestamp (since January 1, 1970) in seconds at which the given key will expire.
/// </summary>
/// <param name="key">The key to get the expiration time for.</param>
/// <param name="storeType">The type of store to retrieve the key from.</param>
/// <param name="output">The output containing the expiration time.</param>
/// <returns>The status of the operation.</returns>
GarnetStatus EXPIRETIME(ref SpanByte key, StoreType storeType, ref SpanByteAndMemory output);

/// <summary>
/// Returns the absolute Unix timestamp (since January 1, 1970) in milliseconds at which the given key will expire.
/// </summary>
/// <param name="key">The key to get the expiration time for.</param>
/// <param name="storeType">The type of store to retrieve the key from.</param>
/// <param name="output">The output containing the expiration time.</param>
/// <returns>The status of the operation.</returns>
GarnetStatus PEXPIRETIME(ref SpanByte key, StoreType storeType, ref SpanByteAndMemory output);

#endregion

#region SortedSet Methods

/// <summary>
Expand Down
10 changes: 10 additions & 0 deletions libs/server/Objects/Types/GarnetObjectType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public enum GarnetObjectType : byte
/// </summary>
Set,

/// <summary>
/// Special type indicating EXPIRETIME command
/// </summary>
Expiretime = 0xf9,

/// <summary>
/// Special type indicating PEXPIRETIME command
/// </summary>
PExpiretime = 0xfa,

/// <summary>
/// Special type indicating PERSIST command
/// </summary>
Expand Down
36 changes: 36 additions & 0 deletions libs/server/Resp/KeyAdminCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,5 +404,41 @@ private bool NetworkTTL<TGarnetApi>(RespCommand command, ref TGarnetApi storageA
}
return true;
}

/// <summary>
/// Get the absolute Unix timestamp at which the given key will expire.
/// </summary>
/// <typeparam name="TGarnetApi"></typeparam>
/// <param name="command">either if the call is for EXPIRETIME or PEXPIRETIME command</param>
/// <param name="storageApi"></param>
/// <returns>Returns the absolute Unix timestamp (since January 1, 1970) in seconds or milliseconds at which the given key will expire.</returns>
private bool NetworkEXPIRETIME<TGarnetApi>(RespCommand command, ref TGarnetApi storageApi)
where TGarnetApi : IGarnetApi
{
if (parseState.Count != 1)
{
return AbortWithWrongNumberOfArguments(nameof(RespCommand.EXPIRETIME));
}

var sbKey = parseState.GetArgSliceByRef(0).SpanByte;
var o = new SpanByteAndMemory(dcurr, (int)(dend - dcurr));
var status = command == RespCommand.EXPIRETIME ?
storageApi.EXPIRETIME(ref sbKey, StoreType.All, ref o) :
storageApi.PEXPIRETIME(ref sbKey, StoreType.All, ref o);

if (status == GarnetStatus.OK)
{
if (!o.IsSpanByte)
SendAndReset(o.Memory, o.Length);
else
dcurr += o.Length;
}
else
{
while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_N2, ref dcurr, dend))
SendAndReset();
}
return true;
}
}
}
10 changes: 10 additions & 0 deletions libs/server/Resp/Parser/RespCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum RespCommand : byte
COSCAN,
DBSIZE,
EXISTS,
EXPIRETIME,
GEODIST,
GEOHASH,
GEOPOS,
Expand All @@ -49,6 +50,7 @@ public enum RespCommand : byte
LRANGE,
MEMORY_USAGE,
MGET,
PEXPIRETIME,
PFCOUNT,
PTTL,
SCAN,
Expand Down Expand Up @@ -1329,6 +1331,10 @@ private RespCommand FastParseArrayCommand(ref int count, ref ReadOnlySpan<byte>
{
return RespCommand.SDIFFSTORE;
}
else if (*(ulong*)(ptr + 1) == MemoryMarshal.Read<ulong>("10\r\nEXPI"u8) && *(uint*)(ptr + 9) == MemoryMarshal.Read<uint>("RETIME\r\n"u8))
{
return RespCommand.EXPIRETIME;
}
break;

case 11:
Expand Down Expand Up @@ -1356,6 +1362,10 @@ private RespCommand FastParseArrayCommand(ref int count, ref ReadOnlySpan<byte>
{
return RespCommand.SINTERSTORE;
}
else if (*(ulong*)(ptr + 2) == MemoryMarshal.Read<ulong>("1\r\nPEXPI"u8) && *(uint*)(ptr + 10) == MemoryMarshal.Read<uint>("RETIME\r\n"u8))
{
return RespCommand.PEXPIRETIME;
}
break;

case 12:
Expand Down
58 changes: 58 additions & 0 deletions libs/server/Resp/RespCommandsInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -1623,6 +1623,35 @@
],
"SubCommands": null
},
{
"Command": "EXPIRETIME",
"Name": "EXPIRETIME",
"IsInternal": false,
"Arity": 2,
"Flags": "Fast, ReadOnly",
"FirstKey": 1,
"LastKey": 1,
"Step": 1,
"AclCategories": "Fast, KeySpace, Read",
"Tips": null,
"KeySpecifications": [
{
"BeginSearch": {
"TypeDiscriminator": "BeginSearchIndex",
"Index": 1
},
"FindKeys": {
"TypeDiscriminator": "FindKeysRange",
"LastKey": 0,
"KeyStep": 1,
"Limit": 0
},
"Notes": null,
"Flags": "RO, Access"
}
],
"SubCommands": null
},
{
"Command": "FAILOVER",
"Name": "FAILOVER",
Expand Down Expand Up @@ -3341,6 +3370,35 @@
],
"SubCommands": null
},
{
"Command": "PEXPIRETIME",
"Name": "PEXPIRETIME",
"IsInternal": false,
"Arity": 2,
"Flags": "Fast, ReadOnly",
"FirstKey": 1,
"LastKey": 1,
"Step": 1,
"AclCategories": "Fast, KeySpace, Read",
"Tips": null,
"KeySpecifications": [
{
"BeginSearch": {
"TypeDiscriminator": "BeginSearchIndex",
"Index": 1
},
"FindKeys": {
"TypeDiscriminator": "FindKeysRange",
"LastKey": 0,
"KeyStep": 1,
"Limit": 0
},
"Notes": null,
"Flags": "RO, Access"
}
],
"SubCommands": null
},
{
"Command": "PFADD",
"Name": "PFADD",
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 @@ -516,6 +516,8 @@ private bool ProcessBasicCommands<TGarnetApi>(RespCommand cmd, ref TGarnetApi st
RespCommand.EXISTS => NetworkEXISTS(ref storageApi),
RespCommand.EXPIRE => NetworkEXPIRE(RespCommand.EXPIRE, ref storageApi),
RespCommand.PEXPIRE => NetworkEXPIRE(RespCommand.PEXPIRE, ref storageApi),
RespCommand.EXPIRETIME => NetworkEXPIRETIME(RespCommand.EXPIRETIME, ref storageApi),
RespCommand.PEXPIRETIME => NetworkEXPIRETIME(RespCommand.PEXPIRETIME, ref storageApi),
RespCommand.PERSIST => NetworkPERSIST(ref storageApi),
RespCommand.GETRANGE => NetworkGetRange(ref storageApi),
RespCommand.TTL => NetworkTTL(RespCommand.TTL, ref storageApi),
Expand Down
11 changes: 11 additions & 0 deletions libs/server/Storage/Functions/MainStore/PrivateMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,17 @@ void CopyRespToWithInput(ref SpanByte input, ref SpanByte value, ref SpanByteAnd
(start, end) = NormalizeRange(start, end, len);
CopyRespTo(ref value, ref dst, start, end);
return;

case RespCommand.EXPIRETIME:
var expireTime = ConvertUtils.UnixTimeInSecondsFromTicks(value.MetadataSize > 0 ? value.ExtraMetadata : -1);
CopyRespNumber(expireTime, ref dst);
return;

case RespCommand.PEXPIRETIME:
var pexpireTime = ConvertUtils.UnixTimeInMillisecondsFromTicks(value.MetadataSize > 0 ? value.ExtraMetadata : -1);
CopyRespNumber(pexpireTime, ref dst);
return;

default:
throw new GarnetException("Unsupported operation on input");
}
Expand Down
47 changes: 30 additions & 17 deletions libs/server/Storage/Functions/ObjectStore/ReadMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,40 @@ public bool SingleReader(ref byte[] key, ref ObjectInput input, ref IGarnetObjec

if (input.header.type != 0)
{
if (input.header.type == GarnetObjectType.Ttl || input.header.type == GarnetObjectType.PTtl) // TTL command
switch (input.header.type)
{
var ttlValue = input.header.type == GarnetObjectType.Ttl ?
ConvertUtils.SecondsFromDiffUtcNowTicks(value.Expiration > 0 ? value.Expiration : -1) :
ConvertUtils.MillisecondsFromDiffUtcNowTicks(value.Expiration > 0 ? value.Expiration : -1);
CopyRespNumber(ttlValue, ref dst.spanByteAndMemory);
return true;
}
case GarnetObjectType.Ttl:
var ttlValue = ConvertUtils.SecondsFromDiffUtcNowTicks(value.Expiration > 0 ? value.Expiration : -1);
CopyRespNumber(ttlValue, ref dst.spanByteAndMemory);
return true;
case GarnetObjectType.PTtl:
ttlValue = ConvertUtils.MillisecondsFromDiffUtcNowTicks(value.Expiration > 0 ? value.Expiration : -1);
CopyRespNumber(ttlValue, ref dst.spanByteAndMemory);
return true;

case GarnetObjectType.Expiretime:
var expireTime = ConvertUtils.UnixTimeInSecondsFromTicks(value.Expiration > 0 ? value.Expiration : -1);
CopyRespNumber(expireTime, ref dst.spanByteAndMemory);
return true;
case GarnetObjectType.PExpiretime:
expireTime = ConvertUtils.UnixTimeInMillisecondsFromTicks(value.Expiration > 0 ? value.Expiration : -1);
CopyRespNumber(expireTime, ref dst.spanByteAndMemory);
return true;

if ((byte)input.header.type < CustomCommandManager.StartOffset)
return value.Operate(ref input, ref dst.spanByteAndMemory, out _, out _);
default:
if ((byte)input.header.type < CustomCommandManager.StartOffset)
return value.Operate(ref input, ref dst.spanByteAndMemory, out _, out _);

if (IncorrectObjectType(ref input, value, ref dst.spanByteAndMemory))
return true;
if (IncorrectObjectType(ref input, value, ref dst.spanByteAndMemory))
return true;

(IMemoryOwner<byte> Memory, int Length) outp = (dst.spanByteAndMemory.Memory, 0);
var customObjectCommand = GetCustomObjectCommand(ref input, input.header.type);
var result = customObjectCommand.Reader(key, ref input, value, ref outp, ref readInfo);
dst.spanByteAndMemory.Memory = outp.Memory;
dst.spanByteAndMemory.Length = outp.Length;
return result;
(IMemoryOwner<byte> Memory, int Length) outp = (dst.spanByteAndMemory.Memory, 0);
var customObjectCommand = GetCustomObjectCommand(ref input, input.header.type);
var result = customObjectCommand.Reader(key, ref input, value, ref outp, ref readInfo);
dst.spanByteAndMemory.Memory = outp.Memory;
dst.spanByteAndMemory.Length = outp.Length;
return result;
}
}

dst.garnetObject = value;
Expand Down
Loading

0 comments on commit 41d77ba

Please sign in to comment.