Skip to content

Commit

Permalink
feat: implement UpdateTtl command (#438)
Browse files Browse the repository at this point in the history
This adds the command UpdateTtl, which unconditionally overwrites
the TTL of an item in the cache. Bumps protos version; adds the
command, documentation, and integration tests.
  • Loading branch information
malandis authored May 4, 2023
1 parent c698107 commit 046ebb5
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 7 deletions.
47 changes: 43 additions & 4 deletions src/Momento.Sdk/CacheClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public async Task<ListCachesResponse> ListCachesAsync()
}

/// <inheritdoc />
async Task<CacheKeyExistsResponse> ICacheClient.KeyExistsAsync(string cacheName, byte[] key)
public async Task<CacheKeyExistsResponse> KeyExistsAsync(string cacheName, byte[] key)
{
try
{
Expand All @@ -99,7 +99,7 @@ async Task<CacheKeyExistsResponse> ICacheClient.KeyExistsAsync(string cacheName,
}

/// <inheritdoc />
async Task<CacheKeyExistsResponse> ICacheClient.KeyExistsAsync(string cacheName, string key)
public async Task<CacheKeyExistsResponse> KeyExistsAsync(string cacheName, string key)
{
try
{
Expand All @@ -114,7 +114,7 @@ async Task<CacheKeyExistsResponse> ICacheClient.KeyExistsAsync(string cacheName,
}

/// <inheritdoc />
async Task<CacheKeysExistResponse> ICacheClient.KeysExistAsync(string cacheName, IEnumerable<byte[]> keys)
public async Task<CacheKeysExistResponse> KeysExistAsync(string cacheName, IEnumerable<byte[]> keys)
{
try
{
Expand All @@ -130,7 +130,7 @@ async Task<CacheKeysExistResponse> ICacheClient.KeysExistAsync(string cacheName,
}

/// <inheritdoc />
async Task<CacheKeysExistResponse> ICacheClient.KeysExistAsync(string cacheName, IEnumerable<string> keys)
public async Task<CacheKeysExistResponse> KeysExistAsync(string cacheName, IEnumerable<string> keys)
{
try
{
Expand All @@ -145,6 +145,45 @@ async Task<CacheKeysExistResponse> ICacheClient.KeysExistAsync(string cacheName,
return await this.DataClient.KeysExistAsync(cacheName, keys);
}

/// <inheritdoc />
public async Task<CacheUpdateTtlResponse> UpdateTtlAsync(string cacheName, byte[] key, TimeSpan ttl)
{
try
{
Utils.ArgumentNotNull(cacheName, nameof(cacheName));
Utils.ArgumentNotNull(key, nameof(key));
Utils.ArgumentStrictlyPositive(ttl, nameof(ttl));
}
catch (ArgumentNullException e)
{
return new CacheUpdateTtlResponse.Error(new InvalidArgumentException(e.Message));
}
catch (ArgumentOutOfRangeException e)
{
return new CacheUpdateTtlResponse.Error(new InvalidArgumentException(e.Message));
}
return await this.DataClient.UpdateTtlAsync(cacheName, key, ttl);
}

/// <inheritdoc />
public async Task<CacheUpdateTtlResponse> UpdateTtlAsync(string cacheName, string key, TimeSpan ttl)
{
try
{
Utils.ArgumentNotNull(cacheName, nameof(cacheName));
Utils.ArgumentNotNull(key, nameof(key));
Utils.ArgumentStrictlyPositive(ttl, nameof(ttl));
}
catch (ArgumentNullException e)
{
return new CacheUpdateTtlResponse.Error(new InvalidArgumentException(e.Message));
}
catch (ArgumentOutOfRangeException e)
{
return new CacheUpdateTtlResponse.Error(new InvalidArgumentException(e.Message));
}
return await this.DataClient.UpdateTtlAsync(cacheName, key, ttl);
}

/// <inheritdoc />
public async Task<CacheSetResponse> SetAsync(string cacheName, byte[] key, byte[] value, TimeSpan? ttl = null)
Expand Down
11 changes: 11 additions & 0 deletions src/Momento.Sdk/ICacheClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,17 @@ public interface ICacheClient : IDisposable
/// <inheritdoc cref="KeysExistAsync(string, IEnumerable{byte[]})"/>
public Task<CacheKeysExistResponse> KeysExistAsync(string cacheName, IEnumerable<string> keys);

/// <summary>
/// Overwrites the TTL of a cache item with the provided TTL.
/// </summary>
/// <param name="cacheName">The name of the cache to perform the lookup in.</param>
/// <param name="key">The key to update the TTL for.</param>
/// <param name="ttl">The new TTL for the item.</param>
/// <returns>Task representing the result of the update TTL operation.</returns>
public Task<CacheUpdateTtlResponse> UpdateTtlAsync(string cacheName, byte[] key, TimeSpan ttl);

/// <inheritdoc cref="UpdateTtlAsync(string, byte[], TimeSpan)"/>
public Task<CacheUpdateTtlResponse> UpdateTtlAsync(string cacheName, string key, TimeSpan ttl);

/// <summary>
/// Set the value in cache with a given time to live (TTL) seconds.
Expand Down
7 changes: 7 additions & 0 deletions src/Momento.Sdk/Internal/DataGrpcManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace Momento.Sdk.Internal;
public interface IDataClient
{
public Task<_KeysExistResponse> KeysExistAsync(_KeysExistRequest request, CallOptions callOptions);
public Task<_UpdateTtlResponse> UpdateTtlAsync(_UpdateTtlRequest request, CallOptions callOptions);
public Task<_GetResponse> GetAsync(_GetRequest request, CallOptions callOptions);
public Task<_SetResponse> SetAsync(_SetRequest request, CallOptions callOptions);
public Task<_DeleteResponse> DeleteAsync(_DeleteRequest request, CallOptions callOptions);
Expand Down Expand Up @@ -74,6 +75,12 @@ public async Task<_KeysExistResponse> KeysExistAsync(_KeysExistRequest request,
return await wrapped.ResponseAsync;
}

public async Task<_UpdateTtlResponse> UpdateTtlAsync(_UpdateTtlRequest request, CallOptions callOptions)
{
var wrapped = await _middlewares.WrapRequest(request, callOptions, (r, o) => _generatedClient.UpdateTtlAsync(r, o));
return await wrapped.ResponseAsync;
}

public async Task<_DeleteResponse> DeleteAsync(_DeleteRequest request, CallOptions callOptions)
{
var wrapped = await _middlewares.WrapRequest(request, callOptions, (r, o) => _generatedClient.DeleteAsync(r, o));
Expand Down
50 changes: 48 additions & 2 deletions src/Momento.Sdk/Internal/ScsDataClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ public async Task<CacheKeysExistResponse> KeysExistAsync(string cacheName, IEnum
return await this.SendKeysExistAsync(cacheName, keys: keys.ToEnumerableByteString());
}

public async Task<CacheUpdateTtlResponse> UpdateTtlAsync(string cacheName, byte[] key, TimeSpan ttl)
{
return await this.SendUpdateTtlAsync(cacheName, key: key.ToByteString(), ttl: ttl);
}

public async Task<CacheUpdateTtlResponse> UpdateTtlAsync(string cacheName, string key, TimeSpan ttl)
{
return await this.SendUpdateTtlAsync(cacheName, key: key.ToByteString(), ttl: ttl);
}

public async Task<CacheSetResponse> SetAsync(string cacheName, byte[] key, byte[] value, TimeSpan? ttl = null)
{
return await this.SendSetAsync(cacheName, key: key.ToByteString(), value: value.ToByteString(), ttl: ttl);
Expand Down Expand Up @@ -412,6 +422,42 @@ private async Task<CacheKeysExistResponse> SendKeysExistAsync(string cacheName,
return this._logger.LogTraceRequestSuccess(REQUEST_TYPE_KEYS_EXIST, cacheName, keys.ToString().ToByteString(), null, null, new CacheKeysExistResponse.Success(keys, response));
}

const string REQUEST_TYPE_UPDATE_TTL = "UPDATE_TTL";
private async Task<CacheUpdateTtlResponse> SendUpdateTtlAsync(string cacheName, ByteString key, TimeSpan ttl)
{
_UpdateTtlRequest request = new _UpdateTtlRequest()
{
CacheKey = key,
OverwriteToMilliseconds = TtlToMilliseconds(ttl)
};
_UpdateTtlResponse response;
var metadata = MetadataWithCache(cacheName);
try
{
this._logger.LogTraceExecutingRequest(REQUEST_TYPE_UPDATE_TTL, cacheName, key, null, ttl);
response = await this.grpcManager.Client.UpdateTtlAsync(request, new CallOptions(headers: metadata, deadline: CalculateDeadline()));
}
catch (Exception e)
{
return this._logger.LogTraceRequestError(REQUEST_TYPE_UPDATE_TTL, cacheName, key, null, ttl, new CacheUpdateTtlResponse.Error(_exceptionMapper.Convert(e, metadata)));
}

if (response.ResultCase == _UpdateTtlResponse.ResultOneofCase.Set)
{
return this._logger.LogTraceRequestSuccess(REQUEST_TYPE_UPDATE_TTL, cacheName, key, null, ttl, new CacheUpdateTtlResponse.Set());
}
else if (response.ResultCase == _UpdateTtlResponse.ResultOneofCase.Missing)
{
return this._logger.LogTraceRequestSuccess(REQUEST_TYPE_UPDATE_TTL, cacheName, key, null, ttl, new CacheUpdateTtlResponse.Miss());
}
else
{
// The other alternative is "NotSet", which is impossible when doing OverwriteTtl.
return this._logger.LogTraceRequestError(REQUEST_TYPE_UPDATE_TTL, cacheName, key, null, ttl, new CacheUpdateTtlResponse.Error(
_exceptionMapper.Convert(new Exception("Unknown response type"), metadata)));
}
}

const string REQUEST_TYPE_SET = "SET";
private async Task<CacheSetResponse> SendSetAsync(string cacheName, ByteString key, ByteString value, TimeSpan? ttl = null)
{
Expand Down Expand Up @@ -1058,7 +1104,7 @@ private async Task<CacheListFetchResponse> SendListFetchAsync(string cacheName,

return this._logger.LogTraceCollectionRequestSuccess(REQUEST_TYPE_LIST_FETCH, cacheName, listName, new CacheListFetchResponse.Miss());
}

const string REQUEST_TYPE_LIST_RETAIN = "LIST_RETAIN";
private async Task<CacheListRetainResponse> SendListRetainAsync(string cacheName, string listName, int? startIndex, int? endIndex)
{
Expand Down Expand Up @@ -1095,7 +1141,7 @@ private async Task<CacheListRetainResponse> SendListRetainAsync(string cacheName

return this._logger.LogTraceCollectionRequestSuccess(REQUEST_TYPE_LIST_RETAIN, cacheName, listName, new CacheListRetainResponse.Success());
}

const string REQUEST_TYPE_LIST_REMOVE_VALUE = "LIST_REMOVE_VALUE";
private async Task<CacheListRemoveValueResponse> SendListRemoveValueAsync(string cacheName, string listName, ByteString value)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Momento.Sdk/Momento.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.Net.Client" Version="2.49.0" />
<PackageReference Include="Momento.Protos" Version="0.51.1" />
<PackageReference Include="Momento.Protos" Version="0.60.1" />
<PackageReference Include="JWT" Version="9.0.3" />
<PackageReference Include="System.Threading.Channels" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
Expand Down
78 changes: 78 additions & 0 deletions src/Momento.Sdk/Responses/CacheUpdateTtlResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
namespace Momento.Sdk.Responses;

using Momento.Sdk.Exceptions;


/// <summary>
/// Parent response type for a cache update ttl request. The
/// response object is resolved to a type-safe object of one of
/// the following subtypes:
/// <list type="bullet">
/// <item><description>CacheUpdateTtlResponse.Set</description></item>
/// <item><description>CacheUpdateTtlResponse.Miss</description></item>
/// <item><description>CacheUpdateTtlResponse.Error</description></item>
/// </list>
/// Pattern matching can be used to operate on the appropriate subtype.
/// For example:
/// <code>
/// if (response is CacheUpdateTtlResponse.Set setResponse)
/// {
/// // handle ttl updated as appropriate
/// }
/// else if (response is CacheUpdateTtlResponse.Miss missResponse)
/// {
/// // handle ttl not updated because key was not found as appropriate
/// }
/// else if (response is CacheUpdateTtlResponse.Error errorResponse)
/// {
/// // handle error as appropriate
/// return errorResponse.Message;
/// }
/// else
/// {
/// // handle unexpected response
/// }
/// </code>
/// </summary>
public abstract class CacheUpdateTtlResponse
{
/// <summary>
/// Indicates the key was found in the cache and the ttl was updated.
/// </summary>
public class Set : CacheUpdateTtlResponse { }

/// <summary>
/// Indicates the key was not found in the cache, hence the ttl was not updated.
/// </summary>
public class Miss : CacheUpdateTtlResponse { }

/// <include file = "../docs.xml" path='docs/class[@name="Error"]/description/*' />
public class Error : CacheUpdateTtlResponse, IError
{
private readonly SdkException _error;

/// <include file = "../docs.xml" path='docs/class[@name="Error"]/constructor/*' />
public Error(SdkException error)
{
_error = error;
}

/// <inheritdoc />
public SdkException InnerException
{
get => _error;
}

/// <inheritdoc />
public MomentoErrorCode ErrorCode
{
get => _error.ErrorCode;
}

/// <inheritdoc />
public string Message
{
get => _error.Message;
}
}
}
Loading

0 comments on commit 046ebb5

Please sign in to comment.