Skip to content

Commit

Permalink
feat: add sortedSetGetScore (#221)
Browse files Browse the repository at this point in the history
Adds the sortedSetGetScore method, response type, and tests to the
client.
  • Loading branch information
malandis authored Oct 16, 2024
1 parent 12d92fe commit 0cde7a4
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 1 deletion.
56 changes: 56 additions & 0 deletions src/Cache/CacheClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
use Momento\Cache\CacheOperationTypes\SetResponse;
use Momento\Cache\CacheOperationTypes\SortedSetFetchResponse;
use Momento\Cache\CacheOperationTypes\SortedSetPutElementResponse;
use Momento\Cache\CacheOperationTypes\SortedSetGetScoreResponse;
use Momento\Cache\CacheOperationTypes\CreateCacheResponse;
use Momento\Cache\CacheOperationTypes\DeleteCacheResponse;
use Momento\Cache\CacheOperationTypes\ListCachesResponse;
Expand Down Expand Up @@ -1841,6 +1842,61 @@ public function sortedSetFetchByRank(string $cacheName, string $sortedSetName, ?
return $this->sortedSetFetchByRankAsync($cacheName, $sortedSetName, $startRank, $endRank, $order)->wait();
}

/**
* Get the score associated with a value in a sorted set.
*
* @param string $cacheName Name of the cache that contains the sorted set.
* @param string $sortedSetName The sorted set to fetch the score from.
* @param string $value The value to fetch the score for.
* @return ResponseFuture<SortedSetGetScoreResponse> A waitable future which will provide the result of the sorted set get score operation upon a blocking call to wait:<br />
* <code>$response = $responseFuture->wait();</code><br />
* The response represents the result of the sorted set get score operation. This result is resolved to a type-safe object of one of the following types:<br>
* * SortedSetGetScoreHit<br>
* * SortedSetGetScoreMiss<br>
* * SortedSetGetScoreError<br>
* Pattern matching can be to operate on the appropriate subtype:<br>
* <code>
* if ($hit = $response->asHit()) {
* $score = $hit->score();
* } elseif ($miss = $response->asMiss()) {
* // handle miss response
* } elseif ($error = $response->asError()) {
* // handle error condition
* }
* </code>
* If inspection of the response is not required, one need not call wait as we implicitly wait for completion of the request on destruction of the response future.
*/
public function sortedSetGetScoreAsync(string $cacheName, string $sortedSetName, string $value): ResponseFuture
{
return $this->getNextDataClient()->sortedSetGetScore($cacheName, $sortedSetName, $value);
}

/**
* Get the score associated with a value in a sorted set.
*
* @param string $cacheName Name of the cache that contains the sorted set.
* @param string $sortedSetName The sorted set to fetch the score from.
* @param string $value The value to fetch the score for.
* @return SortedSetGetScoreResponse Represents the result of the sorted set get score operation. This result is resolved to a type-safe object of one of the following types:<br>
* * SortedSetGetScoreHit<br>
* * SortedSetGetScoreMiss<br>
* * SortedSetGetScoreError<br>
* Pattern matching can be to operate on the appropriate subtype:<br>
* <code>
* if ($hit = $response->asHit()) {
* $score = $hit->score();
* } elseif ($miss = $response->asMiss()) {
* // handle miss response
* } elseif ($error = $response->asError()) {
* // handle error condition
* }
* </code>
*/
public function sortedSetGetScore(string $cacheName, string $sortedSetName, string $value): SortedSetGetScoreResponse
{
return $this->sortedSetGetScoreAsync($cacheName, $sortedSetName, $value)->wait();
}

private function getNextDataClient(): ScsDataClient
{
$client = $this->dataClients[$this->nextDataClientIndex]->getClient();
Expand Down
113 changes: 112 additions & 1 deletion src/Cache/CacheOperationTypes/CacheOperationTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
use Cache_client\_SetContainsResponse;
use Cache_client\_SetFetchResponse;
use Cache_client\_SetLengthResponse;
use Cache_client\_SetResponse;
use Cache_client\_SortedSetFetchResponse;
use Cache_client\ECacheResult;
use Closure;
Expand All @@ -40,6 +39,10 @@ trait ErrorBody
public function __construct(SdkError $error)
{
parent::__construct();
$this->setError($error);
}

public function setError(SdkError $error) {
$this->innerException = $error;
$this->message = "{$error->messageWrapper}: {$error->getMessage()}";
$this->errorCode = $error->errorCode;
Expand Down Expand Up @@ -3397,6 +3400,114 @@ class SortedSetFetchError extends SortedSetFetchResponse
use ErrorBody;
}

/**
* Parent response type for a sorted get score request. The
* response object is resolved to a type-safe object of one of
* the following subtypes:
* * SortedSetGetScoreHit
* * SortedSetGetScoreMiss
* * SortedSetGetScoreError
*
* Pattern matching can be used to operate on the appropriate subtype.
* For example:
* <code>
* if ($success = $response->asHit()) {
* return $success->score();
* } elseif ($response->asMiss())
* // handle miss as appropriate
* } elseif ($error = $response->asError())
* // handle error as appropriate
* }
* </code>
*/
class SortedSetGetScoreResponse extends ResponseBase {
private string $value;

protected function __construct(string $value)
{
parent::__construct();
$this->value = $value;
}

/**
* @return string Value of the requested element.
*/
public function valueString(): string
{
return $this->value;
}

/**
* @return SortedSetGetScoreHit|null Returns the hit subtype if the request returned an error and null otherwise.
*/
public function asHit(): ?SortedSetGetScoreHit {
if ($this->isHit()) {
return $this;
}
return null;
}

/**
* @return SortedSetGetScoreMiss|null Returns the miss subtype if the request returned an error and null otherwise.
*/
public function asMiss(): ?SortedSetGetScoreMiss {
if ($this->isMiss()) {
return $this;
}
return null;
}

/**
* @return SortedSetGetScoreError|null Returns the error subtype if the request returned an error and null otherwise.
*/
public function asError(): ?SortedSetGetScoreError {
if ($this->isError()) {
return $this;
}
return null;
}
}

class SortedSetGetScoreHit extends SortedSetGetScoreResponse {
private float $score;

public function __construct(string $value, float $score)
{
parent::__construct($value);
$this->score = $score;
}

/**
* @return float The score of the requested element.
*/
public function score(): float
{
return $this->score;
}

public function __toString()
{
return parent::__toString() . ": " . $this->score;
}
}

class SortedSetGetScoreMiss extends SortedSetGetScoreResponse {
public function __construct(string $value)
{
parent::__construct($value);
}
}

class SortedSetGetScoreError extends SortedSetGetScoreResponse {
use ErrorBody;

public function __construct(string $value, SdkError $error)
{
parent::__construct($value);
$this->setError($error);
}
}

abstract class GetBatchResponse extends ResponseBase
{
/**
Expand Down
56 changes: 56 additions & 0 deletions src/Cache/Internal/ScsDataClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use Cache_client\_SetUnionRequest;
use Cache_client\_SortedSetElement;
use Cache_client\_SortedSetFetchRequest;
use Cache_client\_SortedSetGetScoreRequest;
use Cache_client\_SortedSetPutRequest;
use Cache_client\_UpdateTtlRequest;
use Cache_client\ECacheResult;
Expand Down Expand Up @@ -190,6 +191,10 @@
use Momento\Cache\CacheOperationTypes\SortedSetFetchHit;
use Momento\Cache\CacheOperationTypes\SortedSetFetchMiss;
use Momento\Cache\CacheOperationTypes\SortedSetFetchResponse;
use Momento\Cache\CacheOperationTypes\SortedSetGetScoreHit;
use Momento\Cache\CacheOperationTypes\SortedSetGetScoreMiss;
use Momento\Cache\CacheOperationTypes\SortedSetGetScoreError;
use Momento\Cache\CacheOperationTypes\SortedSetGetScoreResponse;
use Momento\Cache\CacheOperationTypes\SortedSetPutElementError;
use Momento\Cache\CacheOperationTypes\SortedSetPutElementResponse;
use Momento\Cache\CacheOperationTypes\SortedSetPutElementSuccess;
Expand Down Expand Up @@ -1635,6 +1640,57 @@ function () use ($call): SortedSetFetchResponse {
);
}

/**
* @return ResponseFuture<SortedSetGetScoreResponse>
*/
public function sortedSetGetScore(string $cacheName, string $sortedSetName, string $value): ResponseFuture
{
try {
validateCacheName($cacheName);
validateSortedSetName($sortedSetName);
validateValueName($value);
$sortedSetGetScoreRequest = new _SortedSetGetScoreRequest();
$sortedSetGetScoreRequest->setSetName($sortedSetName);
$sortedSetGetScoreRequest->setValues([$value]);
$call = $this->grpcManager->client->SortedSetGetScore(
$sortedSetGetScoreRequest,
["cache" => [$cacheName]],
["timeout" => $this->timeout],
);
} catch (SdkError $e) {
return ResponseFuture::createResolved(new SortedSetGetScoreError($value, $e));
} catch (Exception $e) {
return ResponseFuture::createResolved(new SortedSetGetScoreError($value, new UnknownError($e->getMessage(), 0, $e)));
}

return ResponseFuture::createPending(
function () use ($call, $value): SortedSetGetScoreResponse {
try {
$response = $this->processCall($call);

if ($response->hasFound()) {
if ($response->getFound()->getElements()->count() == 0) {
return new SortedSetGetScoreError($value, new UnknownError("_SortedSetGetScoreResponseResponse contained no data but was found"));
} else {
$element = $response->getFound()->getElements()[0];
if ($element->getResult() == ECacheResult::Hit) {
return new SortedSetGetScoreHit($value, $element->getScore());
} else {
return new SortedSetGetScoreMiss($value);
}
}
} else {
return new SortedSetGetScoreMiss($value);
}
} catch (SdkError $e) {
return new SortedSetGetScoreError($value, $e);
} catch (Exception $e) {
return new SortedSetGetScoreError($value, new UnknownError($e->getMessage(), 0, $e));
}
}
);
}

/**
* @return ResponseFuture<GetBatchResponse>
*/
Expand Down
77 changes: 77 additions & 0 deletions tests/Cache/CacheClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3297,6 +3297,83 @@ public function testSortedSetFetchByRankWithNegativeStartRankLessThanEndRank_Thr
$this->assertEquals(MomentoErrorCode::INVALID_ARGUMENT_ERROR, $response->asError()->errorCode());
}

public function testSortedSetGetScore_HappyPath()
{
$sortedSetName = uniqid();
$value = uniqid();
$score = 1.0;

// Add the element to the sorted set
$response = $this->client->sortedSetPutElement($this->TEST_CACHE_NAME, $sortedSetName, $value, $score);
$this->assertNull($response->asError());
$this->assertNotNull($response->asSuccess(), "Expected a success but got: $response");

// Fetch the score for the element
$response = $this->client->sortedSetGetScore($this->TEST_CACHE_NAME, $sortedSetName, $value);
$this->assertNull($response->asError());
$this->assertNotNull($response->asHit(), "Expected a hit but got: $response");

$hit = $response->asHit();
$this->assertEquals($value, $hit->valueString(), "The value does not match the expected value.");
$this->assertEquals($score, $hit->score(), "The score does not match the expected value.");
}

public function testSortedSetGetScore_Miss()
{
// 1. First test on a non-existent sorted set
$sortedSetName = uniqid();
$value = uniqid();

// Ensure the sorted set does not exist
$response = $this->client->sortedSetGetScore($this->TEST_CACHE_NAME, $sortedSetName, $value);
$this->assertNull($response->asError());
$this->assertNotNull($response->asMiss(), "Expected a miss but got: $response");

// 2. Second test a sorted set that exists but lacks the element
$sortedSetName = uniqid();
$value = uniqid();
$score = 1.0;

// Add the element to the sorted set
$response = $this->client->sortedSetPutElement($this->TEST_CACHE_NAME, $sortedSetName, $value, $score);
$this->assertNull($response->asError());

// Ensure the element does not exist in the sorted set
$queriedValue = uniqid();
$response = $this->client->sortedSetGetScore($this->TEST_CACHE_NAME, $sortedSetName, $queriedValue);
$this->assertNull($response->asError());
$this->assertNotNull($response->asMiss(), "Expected a miss but got: $response");

$miss = $response->asMiss();
$this->assertEquals($queriedValue, $miss->valueString(), "The value does not match the expected value.");
}

public function testSortedSetGetScore_Error()
{
$sortedSetName = uniqid();
$value = uniqid();

// Using an invalid cache name to trigger an error
$response = $this->client->sortedSetGetScore("", $sortedSetName, $value);
$this->assertNotNull($response->asError(), "Expected an error but got: $response");

$error = $response->asError();
$this->assertEquals($value, $error->valueString());
$this->assertEquals(MomentoErrorCode::INVALID_ARGUMENT_ERROR, $error->errorCode());
}

public function testSortedSetGetScore_NonexistentCache()
{
$sortedSetName = uniqid();
$value = uniqid();
$cacheName = uniqid(); // Non-existent cache

// Fetch the score for the element in a non-existent cache
$response = $this->client->sortedSetGetScore($cacheName, $sortedSetName, $value);
$this->assertNotNull($response->asError(), "Expected an error but got: $response");
$this->assertEquals(MomentoErrorCode::CACHE_NOT_FOUND_ERROR, $response->asError()->errorCode());
}

public function testGetBatch_HappyPath()
{
$cacheName = $this->TEST_CACHE_NAME;
Expand Down

0 comments on commit 0cde7a4

Please sign in to comment.