Skip to content

Commit

Permalink
feat: Add support for Dictionary Length API
Browse files Browse the repository at this point in the history
  • Loading branch information
rishtigupta committed Jun 5, 2023
1 parent c3133d4 commit ef51677
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 1 deletion.
16 changes: 16 additions & 0 deletions src/Momento.Sdk/CacheClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,22 @@ public async Task<CacheDictionaryRemoveFieldsResponse> DictionaryRemoveFieldsAsy
return await this.DataClient.DictionaryRemoveFieldsAsync(cacheName, dictionaryName, fields);
}

/// <inheritdoc />
public async Task<CacheDictionaryLengthResponse> DictionaryLengthAsync(string cacheName, string dictionaryName)
{
try
{
Utils.ArgumentNotNull(cacheName, nameof(cacheName));
Utils.ArgumentNotNull(dictionaryName, nameof(dictionaryName));
}
catch (ArgumentNullException e)
{
return new CacheDictionaryLengthResponse.Error(new InvalidArgumentException(e.Message));
}

return await this.DataClient.DictionaryLengthAsync(cacheName, dictionaryName);
}

/// <inheritdoc />
public async Task<CacheSetAddElementResponse> SetAddElementAsync(string cacheName, string setName, byte[] element, CollectionTtl ttl = default(CollectionTtl))
{
Expand Down
10 changes: 10 additions & 0 deletions src/Momento.Sdk/ICacheClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,16 @@ public interface ICacheClient : IDisposable
/// <inheritdoc cref="DictionaryRemoveFieldsAsync(string, string, IEnumerable{byte[]})"/>
public Task<CacheDictionaryRemoveFieldsResponse> DictionaryRemoveFieldsAsync(string cacheName, string dictionaryName, IEnumerable<string> fields);

/// <summary>
/// Calculate the length of a dictionary in the cache.
///
/// A dictionary that does not exist is interpreted to have length 0.
/// </summary>
/// <param name="cacheName">Name of the cache to perform the lookup in.</param>
/// <param name="dictionaryName">The dictionary to calculate length.</param>
/// <returns>Task representing the length of the dictionary.</returns>
public Task<CacheDictionaryLengthResponse> DictionaryLengthAsync(string cacheName, string dictionaryName);

/// <summary>
/// Add an element to a set in the cache.
///
Expand Down
8 changes: 8 additions & 0 deletions src/Momento.Sdk/Internal/DataGrpcManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public interface IDataClient
public Task<_DictionaryGetResponse> DictionaryGetAsync(_DictionaryGetRequest request, CallOptions callOptions);
public Task<_DictionaryFetchResponse> DictionaryFetchAsync(_DictionaryFetchRequest request, CallOptions callOptions);
public Task<_DictionaryDeleteResponse> DictionaryDeleteAsync(_DictionaryDeleteRequest request, CallOptions callOptions);
public Task<_DictionaryLengthResponse> DictionaryLengthAsync(_DictionaryLengthRequest request, CallOptions callOptions);

public Task<_SetUnionResponse> SetUnionAsync(_SetUnionRequest request, CallOptions callOptions);
public Task<_SetDifferenceResponse> SetDifferenceAsync(_SetDifferenceRequest request, CallOptions callOptions);
public Task<_SetFetchResponse> SetFetchAsync(_SetFetchRequest request, CallOptions callOptions);
Expand Down Expand Up @@ -141,6 +143,12 @@ public async Task<_DictionaryDeleteResponse> DictionaryDeleteAsync(_DictionaryDe
return await wrapped.ResponseAsync;
}

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

public async Task<_SetUnionResponse> SetUnionAsync(_SetUnionRequest request, CallOptions callOptions)
{
var wrapped = await _middlewares.WrapRequest(request, callOptions, (r, o) => _generatedClient.SetUnionAsync(r, o));
Expand Down
30 changes: 30 additions & 0 deletions src/Momento.Sdk/Internal/ScsDataClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ public async Task<CacheDictionaryRemoveFieldsResponse> DictionaryRemoveFieldsAsy
return await SendDictionaryRemoveFieldsAsync(cacheName, dictionaryName, fields.ToEnumerableByteString());
}

public async Task<CacheDictionaryLengthResponse> DictionaryLengthAsync(string cacheName, string dictionaryName)
{
return await SendDictionaryLengthAsync(cacheName, dictionaryName);
}

public async Task<CacheSetAddElementResponse> SetAddElementAsync(string cacheName, string setName, byte[] element, CollectionTtl ttl = default(CollectionTtl))
{
return await SendSetAddElementAsync(cacheName, setName, element.ToSingletonByteString(), ttl);
Expand Down Expand Up @@ -783,6 +788,31 @@ private async Task<CacheDictionaryRemoveFieldsResponse> SendDictionaryRemoveFiel
return this._logger.LogTraceCollectionRequestSuccess(REQUEST_TYPE_DICTIONARY_REMOVE_FIELDS, cacheName, dictionaryName, fields, null, new CacheDictionaryRemoveFieldsResponse.Success());
}

const string REQUEST_TYPE_DICTIONARY_LENGTH = "DICTIONARY_LENGTH";
private async Task<CacheDictionaryLengthResponse> SendDictionaryLengthAsync(string cacheName, string dictionaryName)
{
_DictionaryLengthRequest request = new() { DictionaryName = dictionaryName.ToByteString() };
_DictionaryLengthResponse response;
var metadata = MetadataWithCache(cacheName);

try
{
this._logger.LogTraceExecutingCollectionRequest(REQUEST_TYPE_DICTIONARY_LENGTH, cacheName, dictionaryName);
response = await this.grpcManager.Client.DictionaryLengthAsync(request, new CallOptions(headers: MetadataWithCache(cacheName), deadline: CalculateDeadline()));
}
catch (Exception e)
{
return this._logger.LogTraceCollectionRequestError(REQUEST_TYPE_DICTIONARY_LENGTH, cacheName, dictionaryName, new CacheDictionaryLengthResponse.Error(_exceptionMapper.Convert(e, metadata)));
}

if (response.DictionaryCase == _DictionaryLengthResponse.DictionaryOneofCase.Missing)
{
return this._logger.LogTraceCollectionRequestSuccess(REQUEST_TYPE_DICTIONARY_LENGTH, cacheName, dictionaryName, new CacheDictionaryLengthResponse.Miss());
}

return this._logger.LogTraceCollectionRequestSuccess(REQUEST_TYPE_DICTIONARY_LENGTH, cacheName, dictionaryName, new CacheDictionaryLengthResponse.Hit(response));
}

const string REQUEST_TYPE_SET_ADD_ELEMENT = "SET_ADD_ELEMENT";
private async Task<CacheSetAddElementResponse> SendSetAddElementAsync(string cacheName, string setName, IEnumerable<ByteString> elements, CollectionTtl ttl)
{
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.60.1" />
<PackageReference Include="Momento.Protos" Version="0.64.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
105 changes: 105 additions & 0 deletions src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using Momento.Protos.CacheClient;
using Momento.Sdk.Exceptions;

namespace Momento.Sdk.Responses;

/// <summary>
/// Parent response type for a cache dictionary length request. The
/// response object is resolved to a type-safe object of one of
/// the following subtypes:
/// <list type="bullet">
/// <item><description>CacheDictionaryLengthResponse.Hit</description></item>
/// <item><description>CacheDictionaryLengthResponse.Miss</description></item>
/// <item><description>CacheDictionaryLengthResponse.Error</description></item>
/// </list>
/// Pattern matching can be used to operate on the appropriate subtype.
/// For example:
/// <code>
/// if (response is CacheDictionaryLengthResponse.Hit hitResponse)
/// {
/// return hitResponse.Length;
/// }
/// else if (response is CacheListLengthResponse.Miss missResponse)
/// {
/// // handle missResponse as appropriate
/// }
/// else if (response is CacheListLengthResponse.Error errorResponse)
/// {
/// // handle error as appropriate
/// }
/// else
/// {
/// // handle unexpected response
/// }
/// </code>
/// </summary>
public abstract class CacheDictionaryLengthResponse
{
/// <include file="../docs.xml" path='docs/class[@name="Hit"]/description/*' />
public class Hit : CacheDictionaryLengthResponse
{
/// <summary>
/// The length of the dictionary. If the dictionary is missing or empty, the result is zero.
/// </summary>
public int Length { get; private set; } = 0;

/// <summary>
///
/// </summary>
/// <param name="response">The cache response.</param>
public Hit(_DictionaryLengthResponse response)
{
if(response.DictionaryCase == _DictionaryLengthResponse.DictionaryOneofCase.Found) {
Length = checked((int)response.Found.Length);
}
}

/// <inheritdoc />
public override string ToString()
{
return $"{base.ToString()}: Length: {Length}";
}
}

/// <include file="../docs.xml" path='docs/class[@name="Miss"]/description/*' />
public class Miss : CacheDictionaryLengthResponse
{

}

/// <include file="../docs.xml" path='docs/class[@name="Error"]/description/*' />
public class Error : CacheDictionaryLengthResponse, 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.MessageWrapper}: {_error.Message}";
}

/// <inheritdoc />
public override string ToString()
{
return $"{base.ToString()}: {this.Message}";
}
}
}
23 changes: 23 additions & 0 deletions tests/Integration/Momento.Sdk.Tests/DictionaryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,4 +1118,27 @@ public async Task DictionaryRemoveFieldsAsync_FieldsAreString_HappyPath()
Assert.True((await client.DictionaryGetFieldAsync(cacheName, dictionaryName, fields[1])) is CacheDictionaryGetFieldResponse.Miss);
Assert.True((await client.DictionaryGetFieldAsync(cacheName, dictionaryName, otherField)) is CacheDictionaryGetFieldResponse.Hit);
}

[Fact]
public async Task DictionaryLengthAsync_DictionaryIsMissing()
{
var dictionaryName = Utils.NewGuidString();
CacheDictionaryLengthResponse response = await client.DictionaryLengthAsync(cacheName, dictionaryName);
Assert.True(response is CacheDictionaryLengthResponse.Miss, $"Unexpected response: {response}");
}

[Fact]
public async Task DictionaryLengthAsync_HappyPath()
{
var dictionaryName = Utils.NewGuidString();
var field = Utils.NewGuidByteArray();
var value = Utils.NewGuidByteArray();

await client.DictionarySetFieldAsync(cacheName, dictionaryName, field, value);

CacheDictionaryLengthResponse lengthResponse = await client.DictionaryLengthAsync(cacheName, dictionaryName);
Assert.True(lengthResponse is CacheDictionaryLengthResponse.Hit, $"Unexpected response: {lengthResponse}");
var hitResponse = (CacheDictionaryLengthResponse.Hit)lengthResponse;
Assert.Equal(1, hitResponse.Length);
}
}

0 comments on commit ef51677

Please sign in to comment.