From ef51677abd5a05a47960c63734d9def90a06948e Mon Sep 17 00:00:00 2001 From: rishtigupta Date: Mon, 5 Jun 2023 14:56:45 -0700 Subject: [PATCH 1/2] feat: Add support for Dictionary Length API --- src/Momento.Sdk/CacheClient.cs | 16 +++ src/Momento.Sdk/ICacheClient.cs | 10 ++ src/Momento.Sdk/Internal/DataGrpcManager.cs | 8 ++ src/Momento.Sdk/Internal/ScsDataClient.cs | 30 +++++ src/Momento.Sdk/Momento.Sdk.csproj | 2 +- .../CacheDictionaryLengthResponse.cs | 105 ++++++++++++++++++ .../Momento.Sdk.Tests/DictionaryTest.cs | 23 ++++ 7 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs diff --git a/src/Momento.Sdk/CacheClient.cs b/src/Momento.Sdk/CacheClient.cs index 9f2d837e..8cd31238 100644 --- a/src/Momento.Sdk/CacheClient.cs +++ b/src/Momento.Sdk/CacheClient.cs @@ -727,6 +727,22 @@ public async Task DictionaryRemoveFieldsAsy return await this.DataClient.DictionaryRemoveFieldsAsync(cacheName, dictionaryName, fields); } + /// + public async Task 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); + } + /// public async Task SetAddElementAsync(string cacheName, string setName, byte[] element, CollectionTtl ttl = default(CollectionTtl)) { diff --git a/src/Momento.Sdk/ICacheClient.cs b/src/Momento.Sdk/ICacheClient.cs index 305a6610..45fff028 100644 --- a/src/Momento.Sdk/ICacheClient.cs +++ b/src/Momento.Sdk/ICacheClient.cs @@ -452,6 +452,16 @@ public interface ICacheClient : IDisposable /// public Task DictionaryRemoveFieldsAsync(string cacheName, string dictionaryName, IEnumerable fields); + /// + /// Calculate the length of a dictionary in the cache. + /// + /// A dictionary that does not exist is interpreted to have length 0. + /// + /// Name of the cache to perform the lookup in. + /// The dictionary to calculate length. + /// Task representing the length of the dictionary. + public Task DictionaryLengthAsync(string cacheName, string dictionaryName); + /// /// Add an element to a set in the cache. /// diff --git a/src/Momento.Sdk/Internal/DataGrpcManager.cs b/src/Momento.Sdk/Internal/DataGrpcManager.cs index faab8831..d410b327 100644 --- a/src/Momento.Sdk/Internal/DataGrpcManager.cs +++ b/src/Momento.Sdk/Internal/DataGrpcManager.cs @@ -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); @@ -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)); diff --git a/src/Momento.Sdk/Internal/ScsDataClient.cs b/src/Momento.Sdk/Internal/ScsDataClient.cs index e97027eb..fce1274e 100644 --- a/src/Momento.Sdk/Internal/ScsDataClient.cs +++ b/src/Momento.Sdk/Internal/ScsDataClient.cs @@ -258,6 +258,11 @@ public async Task DictionaryRemoveFieldsAsy return await SendDictionaryRemoveFieldsAsync(cacheName, dictionaryName, fields.ToEnumerableByteString()); } + public async Task DictionaryLengthAsync(string cacheName, string dictionaryName) + { + return await SendDictionaryLengthAsync(cacheName, dictionaryName); + } + public async Task SetAddElementAsync(string cacheName, string setName, byte[] element, CollectionTtl ttl = default(CollectionTtl)) { return await SendSetAddElementAsync(cacheName, setName, element.ToSingletonByteString(), ttl); @@ -783,6 +788,31 @@ private async Task 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 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 SendSetAddElementAsync(string cacheName, string setName, IEnumerable elements, CollectionTtl ttl) { diff --git a/src/Momento.Sdk/Momento.Sdk.csproj b/src/Momento.Sdk/Momento.Sdk.csproj index f15d9499..28e4ca1b 100644 --- a/src/Momento.Sdk/Momento.Sdk.csproj +++ b/src/Momento.Sdk/Momento.Sdk.csproj @@ -54,7 +54,7 @@ - + diff --git a/src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs b/src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs new file mode 100644 index 00000000..9b22e5b1 --- /dev/null +++ b/src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs @@ -0,0 +1,105 @@ +using Momento.Protos.CacheClient; +using Momento.Sdk.Exceptions; + +namespace Momento.Sdk.Responses; + +/// +/// 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: +/// +/// CacheDictionaryLengthResponse.Hit +/// CacheDictionaryLengthResponse.Miss +/// CacheDictionaryLengthResponse.Error +/// +/// Pattern matching can be used to operate on the appropriate subtype. +/// For example: +/// +/// 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 +/// } +/// +/// +public abstract class CacheDictionaryLengthResponse +{ + /// + public class Hit : CacheDictionaryLengthResponse + { + /// + /// The length of the dictionary. If the dictionary is missing or empty, the result is zero. + /// + public int Length { get; private set; } = 0; + + /// + /// + /// + /// The cache response. + public Hit(_DictionaryLengthResponse response) + { + if(response.DictionaryCase == _DictionaryLengthResponse.DictionaryOneofCase.Found) { + Length = checked((int)response.Found.Length); + } + } + + /// + public override string ToString() + { + return $"{base.ToString()}: Length: {Length}"; + } + } + + /// + public class Miss : CacheDictionaryLengthResponse + { + + } + + /// + public class Error : CacheDictionaryLengthResponse, IError + { + private readonly SdkException _error; + + /// + public Error(SdkException error) + { + _error = error; + } + + /// + public SdkException InnerException + { + get => _error; + } + + /// + public MomentoErrorCode ErrorCode + { + get => _error.ErrorCode; + } + + /// + public string Message + { + get => $"{_error.MessageWrapper}: {_error.Message}"; + } + + /// + public override string ToString() + { + return $"{base.ToString()}: {this.Message}"; + } + } +} diff --git a/tests/Integration/Momento.Sdk.Tests/DictionaryTest.cs b/tests/Integration/Momento.Sdk.Tests/DictionaryTest.cs index 9122712b..4a7bce2e 100644 --- a/tests/Integration/Momento.Sdk.Tests/DictionaryTest.cs +++ b/tests/Integration/Momento.Sdk.Tests/DictionaryTest.cs @@ -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); + } } From b649b3637c3482c228f2eee13f8ec67cbb8f8dd0 Mon Sep 17 00:00:00 2001 From: rishtigupta Date: Mon, 5 Jun 2023 15:58:09 -0700 Subject: [PATCH 2/2] fix: add api to retry startegy, fix typos --- src/Momento.Sdk/ICacheClient.cs | 1 - .../Internal/Retry/DefaultRetryEligibilityStrategy.cs | 1 + src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Momento.Sdk/ICacheClient.cs b/src/Momento.Sdk/ICacheClient.cs index 45fff028..9c6316fc 100644 --- a/src/Momento.Sdk/ICacheClient.cs +++ b/src/Momento.Sdk/ICacheClient.cs @@ -455,7 +455,6 @@ public interface ICacheClient : IDisposable /// /// Calculate the length of a dictionary in the cache. /// - /// A dictionary that does not exist is interpreted to have length 0. /// /// Name of the cache to perform the lookup in. /// The dictionary to calculate length. diff --git a/src/Momento.Sdk/Internal/Retry/DefaultRetryEligibilityStrategy.cs b/src/Momento.Sdk/Internal/Retry/DefaultRetryEligibilityStrategy.cs index d53af0bd..59ae807f 100644 --- a/src/Momento.Sdk/Internal/Retry/DefaultRetryEligibilityStrategy.cs +++ b/src/Momento.Sdk/Internal/Retry/DefaultRetryEligibilityStrategy.cs @@ -47,6 +47,7 @@ public class DefaultRetryEligibilityStrategy : IRetryEligibilityStrategy typeof(_DictionaryGetRequest), typeof(_DictionaryFetchRequest), typeof(_DictionaryDeleteRequest), + typeof(_DictionaryLengthRequest), typeof(_SetUnionRequest), typeof(_SetDifferenceRequest), typeof(_SetFetchRequest), diff --git a/src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs b/src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs index 9b22e5b1..1673a8ce 100644 --- a/src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs +++ b/src/Momento.Sdk/Responses/CacheDictionaryLengthResponse.cs @@ -19,11 +19,11 @@ namespace Momento.Sdk.Responses; /// { /// return hitResponse.Length; /// } -/// else if (response is CacheListLengthResponse.Miss missResponse) +/// else if (response is CacheDictionaryLengthResponse.Miss missResponse) /// { /// // handle missResponse as appropriate /// } -/// else if (response is CacheListLengthResponse.Error errorResponse) +/// else if (response is CacheDictionaryLengthResponse.Error errorResponse) /// { /// // handle error as appropriate /// }