Skip to content

Commit

Permalink
Merge pull request #528 from momentohq/add-set-sample
Browse files Browse the repository at this point in the history
feat: Implement SetSample
  • Loading branch information
cprice404 authored Feb 8, 2024
2 parents 3fbed5b + 99943f0 commit 81f5293
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 8 deletions.
21 changes: 21 additions & 0 deletions src/Momento.Sdk/CacheClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,27 @@ public async Task<CacheSetFetchResponse> SetFetchAsync(string cacheName, string

return await this.DataClient.SetFetchAsync(cacheName, setName);
}

/// <inheritdoc />
public async Task<CacheSetSampleResponse> SetSampleAsync(string cacheName, string setName, int limit)
{
try
{
Utils.ArgumentNotNull(cacheName, nameof(cacheName));
Utils.ArgumentNotNull(setName, nameof(setName));
Utils.ArgumentNonNegative(limit, nameof(limit));
}
catch (ArgumentNullException e)
{
return new CacheSetSampleResponse.Error(new InvalidArgumentException(e.Message));
}
catch (ArgumentOutOfRangeException e)
{
return new CacheSetSampleResponse.Error(new InvalidArgumentException(e.Message));
}

return await this.DataClient.SetSampleAsync(cacheName, setName, Convert.ToUInt64(limit));
}

/// <inheritdoc />
public async Task<CacheSetLengthResponse> SetLengthAsync(string cacheName, string setName)
Expand Down
17 changes: 13 additions & 4 deletions src/Momento.Sdk/ICacheClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ public interface ICacheClient : IDisposable
/// </summary>
/// <param name="cacheName">Name of the cache to perform the lookup in.</param>
/// <param name="dictionaryName">The dictionary to fetch.</param>
/// <returns>Task representing with the status of the fetch operation and the associated dictionary.</returns>
/// <returns>Task representing the status of the fetch operation and the associated dictionary.</returns>
public Task<CacheDictionaryFetchResponse> DictionaryFetchAsync(string cacheName, string dictionaryName);

/// <summary>
Expand Down Expand Up @@ -540,9 +540,18 @@ public interface ICacheClient : IDisposable
/// </summary>
/// <param name="cacheName">Name of the cache to perform the lookup in.</param>
/// <param name="setName">The set to fetch.</param>
/// <returns>Task representing with the status of the fetch operation and the associated set.</returns>
/// <returns>Task representing the status of the fetch operation and the associated set.</returns>
public Task<CacheSetFetchResponse> SetFetchAsync(string cacheName, string setName);


/// <summary>
/// Fetch a random sample of elements from the set. Returns a different random sample for each call.
/// </summary>
/// <param name="cacheName">Name of the cache to perform the lookup in.</param>
/// <param name="setName">The set to fetch.</param>
/// <param name="limit">The maximum number of elements to return. If the set contains fewer than 'limit' elements, the entire set will be returned.</param>
/// <returns>Task representing the status of the sample operation and the associated set.</returns>
public Task<CacheSetSampleResponse> SetSampleAsync(string cacheName, string setName, int limit);

/// <summary>
/// Calculate the length of a set in the cache.
///
Expand Down Expand Up @@ -634,7 +643,7 @@ public interface ICacheClient : IDisposable
/// <param name="listName">The list to fetch.</param>
/// <param name="startIndex">Start inclusive index for fetch operation. Must be smaller than the endIndex.</param>
/// <param name="endIndex">End exclusive index for fetch operation. Must be larger than the startIndex.</param>
/// <returns>Task representing with the status of the fetch operation and the associated list.</returns>
/// <returns>Task representing the status of the fetch operation and the associated list.</returns>
public Task<CacheListFetchResponse> ListFetchAsync(string cacheName, string listName, int? startIndex = null, int? endIndex = null);

/// <summary>
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 @@ -40,6 +40,7 @@ public interface IDataClient
public Task<_SetUnionResponse> SetUnionAsync(_SetUnionRequest request, CallOptions callOptions);
public Task<_SetDifferenceResponse> SetDifferenceAsync(_SetDifferenceRequest request, CallOptions callOptions);
public Task<_SetFetchResponse> SetFetchAsync(_SetFetchRequest request, CallOptions callOptions);
public Task<_SetSampleResponse> SetSampleAsync(_SetSampleRequest request, CallOptions callOptions);
public Task<_SetLengthResponse> SetLengthAsync(_SetLengthRequest request, CallOptions callOptions);
public Task<_ListPushFrontResponse> ListPushFrontAsync(_ListPushFrontRequest request, CallOptions callOptions);
public Task<_ListPushBackResponse> ListPushBackAsync(_ListPushBackRequest request, CallOptions callOptions);
Expand Down Expand Up @@ -174,6 +175,12 @@ public async Task<_SetFetchResponse> SetFetchAsync(_SetFetchRequest request, Cal
var wrapped = await _middlewares.WrapRequest(request, callOptions, (r, o) => _generatedClient.SetFetchAsync(r, o));
return await wrapped.ResponseAsync;
}

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

public async Task<_SetLengthResponse> SetLengthAsync(_SetLengthRequest request, CallOptions callOptions)
{
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 @@ -318,6 +318,11 @@ public async Task<CacheSetFetchResponse> SetFetchAsync(string cacheName, string
return await SendSetFetchAsync(cacheName, setName);
}

public async Task<CacheSetSampleResponse> SetSampleAsync(string cacheName, string setName, ulong limit)
{
return await SendSetSampleAsync(cacheName, setName, limit);
}

public async Task<CacheSetLengthResponse> SetLengthAsync(string cacheName, string setName)
{
return await SendSetLengthAsync(cacheName, setName);
Expand Down Expand Up @@ -980,7 +985,32 @@ private async Task<CacheSetFetchResponse> SendSetFetchAsync(string cacheName, st

return this._logger.LogTraceCollectionRequestSuccess(REQUEST_TYPE_SET_FETCH, cacheName, setName, new CacheSetFetchResponse.Miss());
}


const string REQUEST_TYPE_SET_SAMPLE = "SET_SAMPLE";
private async Task<CacheSetSampleResponse> SendSetSampleAsync(string cacheName, string setName, ulong limit)
{
_SetSampleRequest request = new() { SetName = setName.ToByteString(), Limit = limit };
_SetSampleResponse response;
var metadata = MetadataWithCache(cacheName);

try
{
this._logger.LogTraceExecutingCollectionRequest(REQUEST_TYPE_SET_SAMPLE, cacheName, setName);
response = await this.grpcManager.Client.SetSampleAsync(request, new CallOptions(headers: MetadataWithCache(cacheName), deadline: CalculateDeadline()));
}
catch (Exception e)
{
return this._logger.LogTraceCollectionRequestError(REQUEST_TYPE_SET_SAMPLE, cacheName, setName, new CacheSetSampleResponse.Error(_exceptionMapper.Convert(e, metadata)));
}
if (response.SetCase == _SetSampleResponse.SetOneofCase.Found)
{
return this._logger.LogTraceCollectionRequestSuccess(REQUEST_TYPE_SET_SAMPLE, cacheName, setName, new CacheSetSampleResponse.Hit(response));
}

return this._logger.LogTraceCollectionRequestSuccess(REQUEST_TYPE_SET_SAMPLE, cacheName, setName, new CacheSetSampleResponse.Miss());
}

const string REQUEST_TYPE_SET_LENGTH = "SET_LENGTH";
private async Task<CacheSetLengthResponse> SendSetLengthAsync(string cacheName, string setName)
{
Expand Down
22 changes: 21 additions & 1 deletion src/Momento.Sdk/Internal/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,27 @@ public static void ArgumentStrictlyPositive(int? argument, string paramName)

if (argument <= 0)
{
throw new ArgumentOutOfRangeException(paramName, "TimeSpan must be strictly positive.");
throw new ArgumentOutOfRangeException(paramName, "int must be strictly positive.");
}
}


/// <summary>
/// Throw an exception if the value is negative.
/// </summary>
/// <param name="argument">The value to test.</param>
/// <param name="paramName">Name of the value to propagate to the exception.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="argument"/> is negative.</exception>
public static void ArgumentNonNegative(int? argument, string paramName)
{
if (argument is null)
{
return;
}

if (argument < 0)
{
throw new ArgumentOutOfRangeException(paramName, "int must be strictly positive.");
}
}

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 @@ -57,7 +57,7 @@
<ItemGroup>
<PackageReference Include="Grpc.Net.Client" Version="2.49.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
<PackageReference Include="Momento.Protos" Version="0.102.1" />
<PackageReference Include="Momento.Protos" Version="0.105.2" />
<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
2 changes: 1 addition & 1 deletion src/Momento.Sdk/Responses/CacheSetFetchResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace Momento.Sdk.Responses;
/// <code>
/// if (response is CacheSetFetchResponse.Hit hitResponse)
/// {
/// return hitResponse.ValueSetStringString;
/// return hitResponse.ValueSetString;
/// }
/// else if (response is CacheSetFetchResponse.Miss missResponse)
/// {
Expand Down
134 changes: 134 additions & 0 deletions src/Momento.Sdk/Responses/CacheSetSampleResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Google.Protobuf;
using Google.Protobuf.Collections;
using Momento.Protos.CacheClient;
using Momento.Sdk.Exceptions;
using Momento.Sdk.Internal;
using Momento.Sdk.Internal.ExtensionMethods;

namespace Momento.Sdk.Responses;

/// <summary>
/// Parent response type for a cache set sample request. The
/// response object is resolved to a type-safe object of one of
/// the following subtypes:
/// <list type="bullet">
/// <item><description>CacheSetSampleResponse.Success</description></item>
/// <item><description>CacheSetSampleResponse.Error</description></item>
/// </list>
/// Pattern matching can be used to operate on the appropriate subtype.
/// For example:
/// <code>
/// if (response is CacheSetSampleResponse.Hit hitResponse)
/// {
/// return hitResponse.ValueSetString;
/// }
/// else if (response is CacheSetSampleResponse.Miss missResponse)
/// {
/// // handle miss as appropriate
/// }
/// else if (response is CacheSetSampleResponse.Error errorResponse)
/// {
/// // handle error as appropriate
/// }
/// else
/// {
/// // handle unexpected response
/// }
/// </code>
/// </summary>
public abstract class CacheSetSampleResponse
{
/// <include file="../docs.xml" path='docs/class[@name="Hit"]/description/*' />
public class Hit : CacheSetSampleResponse
{
#pragma warning disable 1591
protected readonly RepeatedField<ByteString> elements;
protected readonly Lazy<ISet<byte[]>> _byteArraySet;
protected readonly Lazy<ISet<string>> _stringSet;
#pragma warning restore 1591

/// <summary>
///
/// </summary>
/// <param name="response">Cache set sample response.</param>
public Hit(_SetSampleResponse response)
{
elements = response.Found.Elements;
_byteArraySet = new(() =>
{
return new HashSet<byte[]>(
elements.Select(element => element.ToByteArray()),
Utils.ByteArrayComparer);
});

_stringSet = new(() =>
{

return new HashSet<string>(elements.Select(element => element.ToStringUtf8()));
});
}

/// <summary>
/// Randomly sample elements from the Set as a <see cref="ISet{T}" /> of <see cref="byte" /> arrays.
/// </summary>
public ISet<byte[]> ValueSetByteArray { get => _byteArraySet.Value; }

/// <summary>
/// Randomly sample elements from the Set as a <see cref="ISet{T}" /> of <see cref="string" />s.
/// </summary>
public ISet<string> ValueSetString { get => _stringSet.Value; }

/// <inheritdoc />
public override string ToString()
{
var stringRepresentation = String.Join(", ", ValueSetString.Select(value => $"\"{value}\""));
var byteArrayRepresentation = String.Join(", ", ValueSetByteArray.Select(value => $"\"{value.ToPrettyHexString()}\""));
return $"{base.ToString()}: ValueSetString: {{{stringRepresentation.Truncate()}}} ValueSetByteArray: {{{byteArrayRepresentation.Truncate()}}}";
}
}

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

}

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

0 comments on commit 81f5293

Please sign in to comment.