From dce607828781aceba8463c78cec89a85e5910216 Mon Sep 17 00:00:00 2001 From: Nate Anderson Date: Wed, 15 Jan 2025 13:56:17 -0800 Subject: [PATCH] feat: add the remaining leaderboard APIs (#412) * feat: add the remaining leaderboard APIs Add leaderboard fetchByRank, getRank, length, and delete APIs. Set the default leaderboard max received message size to 200MB. Make SortOrder in fetchByRank nullable and have it default to ascending if null. Make the different null check validation methods use the same backing validateNotNull. Add a fetchByScore signature that doesn't take any arguments and fetches the first 8192 elements of a leaderboard. Comment fixes. --- .../leaderboard/LeaderboardClientTest.java | 393 +++++++++++++++++- .../main/java/momento/sdk/ILeaderboard.java | 91 +++- .../main/java/momento/sdk/Leaderboard.java | 45 +- .../momento/sdk/LeaderboardDataClient.java | 283 ++++++++++++- .../main/java/momento/sdk/ScsDataClient.java | 8 +- .../java/momento/sdk/ValidationUtils.java | 60 +-- .../LeaderboardGrpcConfiguration.java | 2 +- .../sdk/internal/GrpcChannelOptions.java | 9 +- .../responses/leaderboard/DeleteResponse.java | 38 ++ .../responses/leaderboard/LengthResponse.java | 53 +++ 10 files changed, 907 insertions(+), 75 deletions(-) create mode 100644 momento-sdk/src/main/java/momento/sdk/responses/leaderboard/DeleteResponse.java create mode 100644 momento-sdk/src/main/java/momento/sdk/responses/leaderboard/LengthResponse.java diff --git a/momento-sdk/src/intTest/java/momento/sdk/leaderboard/LeaderboardClientTest.java b/momento-sdk/src/intTest/java/momento/sdk/leaderboard/LeaderboardClientTest.java index 65c58152..a0b95a37 100644 --- a/momento-sdk/src/intTest/java/momento/sdk/leaderboard/LeaderboardClientTest.java +++ b/momento-sdk/src/intTest/java/momento/sdk/leaderboard/LeaderboardClientTest.java @@ -4,16 +4,20 @@ import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import momento.sdk.ILeaderboard; import momento.sdk.exceptions.CacheNotFoundException; import momento.sdk.exceptions.InvalidArgumentException; import momento.sdk.responses.SortOrder; +import momento.sdk.responses.leaderboard.DeleteResponse; import momento.sdk.responses.leaderboard.FetchResponse; import momento.sdk.responses.leaderboard.LeaderboardElement; +import momento.sdk.responses.leaderboard.LengthResponse; import momento.sdk.responses.leaderboard.RemoveElementsResponse; import momento.sdk.responses.leaderboard.UpsertResponse; import org.assertj.core.api.InstanceOfAssertFactories; @@ -70,6 +74,7 @@ public void upsertHappyPath() { @Test public void upsertInvalidCacheName() { final String leaderboardName = randomString("leaderboard"); + //noinspection DataFlowIssue final ILeaderboard leaderboard = leaderboardClient.leaderboard(null, leaderboardName); final Map elements = new HashMap<>(); @@ -122,6 +127,150 @@ public void upsertInvalidElements() { .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); } + // fetch by score + + @Test + public void fetchByScoreHappyPath() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + final Map elements = new HashMap<>(); + elements.put(1, 1.0); + elements.put(2, 2.0); + elements.put(3, 3.0); + + assertThat(leaderboard.upsert(elements)) + .succeedsWithin(FIVE_SECONDS) + .isInstanceOf(UpsertResponse.Success.class); + + // ascending + assertThat(leaderboard.fetchByScore()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> { + final List scoredElements = resp.elementsList(); + assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(1, 2, 3); + }); + assertThat(leaderboard.fetchByScore(null, null, null, 1, null)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> { + final List scoredElements = resp.elementsList(); + assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(2, 3); + }); + assertThat(leaderboard.fetchByScore(null, null, SortOrder.ASCENDING, 0, 2)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> { + final List scoredElements = resp.elementsList(); + assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(1, 2); + }); + + // descending + assertThat(leaderboard.fetchByScore(null, null, SortOrder.DESCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> { + final List scoredElements = resp.elementsList(); + assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(3, 2, 1); + }); + assertThat(leaderboard.fetchByScore(null, null, SortOrder.DESCENDING, 1, null)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> { + final List scoredElements = resp.elementsList(); + assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(2, 1); + }); + assertThat(leaderboard.fetchByScore(null, null, SortOrder.DESCENDING, 0, 2)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> { + final List scoredElements = resp.elementsList(); + assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(3, 2); + }); + + // limited by max score + assertThat(leaderboard.fetchByScore(null, 2.1, SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> { + final List scoredElements = resp.elementsList(); + assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(1, 2); + }); + + // limited by min score + assertThat(leaderboard.fetchByScore(1.1, null, SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> { + final List scoredElements = resp.elementsList(); + assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(2, 3); + }); + + // limited by min score and max score + assertThat(leaderboard.fetchByScore(1.1, 3.0, SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> { + final List scoredElements = resp.elementsList(); + assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(2); + }); + } + + @Test + public void fetchByScoreInvalidCacheName() { + final String leaderboardName = randomString("leaderboard"); + //noinspection DataFlowIssue + final ILeaderboard leaderboard = leaderboardClient.leaderboard(null, leaderboardName); + + assertThat(leaderboard.fetchByScore(null, null, SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } + + @Test + public void fetchByScoreNonExistentCache() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(randomString(), leaderboardName); + + assertThat(leaderboard.fetchByScore(null, null, SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(CacheNotFoundException.class)); + } + + @Test + public void fetchByScoreInvalidLeaderboardName() { + final String leaderboardName = ""; + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + assertThat(leaderboard.fetchByScore(null, null, SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } + + @Test + public void fetchByScoreInvalidScoreRange() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + assertThat(leaderboard.fetchByScore(10.0, 1.0, SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } + // fetch by rank @Test @@ -138,33 +287,33 @@ public void fetchByRankHappyPath() { .succeedsWithin(FIVE_SECONDS) .isInstanceOf(UpsertResponse.Success.class); + // ascending assertThat(leaderboard.fetchByRank(0, 10, SortOrder.ASCENDING)) .succeedsWithin(FIVE_SECONDS) .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) .satisfies( resp -> { final List scoredElements = resp.elementsList(); - assertThat(scoredElements).hasSize(3); assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(1, 2, 3); }); + // descending assertThat(leaderboard.fetchByRank(0, 10, SortOrder.DESCENDING)) .succeedsWithin(FIVE_SECONDS) .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) .satisfies( resp -> { final List scoredElements = resp.elementsList(); - assertThat(scoredElements).hasSize(3); assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(3, 2, 1); }); - assertThat(leaderboard.fetchByRank(0, 2, SortOrder.ASCENDING)) + // rank range smaller than leaderboard size + assertThat(leaderboard.fetchByRank(0, 2, null)) .succeedsWithin(FIVE_SECONDS) .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) .satisfies( resp -> { final List scoredElements = resp.elementsList(); - assertThat(scoredElements).hasSize(2); assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(1, 2); }); @@ -174,7 +323,6 @@ public void fetchByRankHappyPath() { .satisfies( resp -> { final List scoredElements = resp.elementsList(); - assertThat(scoredElements).hasSize(1); assertThat(scoredElements).map(LeaderboardElement::getId).containsExactly(2); }); } @@ -182,6 +330,7 @@ public void fetchByRankHappyPath() { @Test public void fetchByRankInvalidCacheName() { final String leaderboardName = randomString("leaderboard"); + //noinspection DataFlowIssue final ILeaderboard leaderboard = leaderboardClient.leaderboard(null, leaderboardName); assertThat(leaderboard.fetchByRank(0, 1, SortOrder.ASCENDING)) @@ -228,6 +377,170 @@ public void fetchByRankInvalidRankRange() { .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); } + // get rank + + @Test + public void getRankHappyPath() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + final Map elements = new HashMap<>(); + elements.put(1, 1.0); + elements.put(2, 2.0); + elements.put(3, 3.0); + + assertThat(leaderboard.upsert(elements)) + .succeedsWithin(FIVE_SECONDS) + .isInstanceOf(UpsertResponse.Success.class); + + // ascending + assertThat(leaderboard.getRank(elements.keySet(), SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> + assertThat(resp.elementsList()) + .extracting("id", "rank") + .containsExactly(tuple(1, 0), tuple(2, 1), tuple(3, 2))); + + // descending + assertThat(leaderboard.getRank(elements.keySet(), SortOrder.DESCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> + assertThat(resp.elementsList()) + .extracting("id", "rank") + .containsExactly(tuple(1, 2), tuple(2, 1), tuple(3, 0))); + + // ids are a subset of the leaderboard + assertThat(leaderboard.getRank(new HashSet<>(Arrays.asList(1, 2)), null)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> + assertThat(resp.elementsList()) + .extracting("id", "rank") + .containsExactly(tuple(1, 0), tuple(2, 1))); + + // ids are a superset of the leaderboard + assertThat(leaderboard.getRank(new HashSet<>(Arrays.asList(1, 2, 3, 4)), null)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Success.class)) + .satisfies( + resp -> + assertThat(resp.elementsList()) + .extracting("id", "rank") + .containsExactly(tuple(1, 0), tuple(2, 1), tuple(3, 2))); + } + + @Test + public void getRankInvalidCacheName() { + final String leaderboardName = randomString("leaderboard"); + //noinspection DataFlowIssue + final ILeaderboard leaderboard = leaderboardClient.leaderboard(null, leaderboardName); + + assertThat(leaderboard.getRank(Collections.singleton(1), SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } + + @Test + public void getRankNonExistentCache() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(randomString(), leaderboardName); + + assertThat(leaderboard.getRank(Collections.singleton(1), SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(CacheNotFoundException.class)); + } + + @Test + public void getRankInvalidLeaderboardName() { + final String leaderboardName = ""; + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + assertThat(leaderboard.getRank(Collections.singleton(1), SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } + + @Test + public void getRankInvalidIds() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + //noinspection DataFlowIssue + assertThat(leaderboard.getRank(null, SortOrder.ASCENDING)) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(FetchResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } + + // length + + @Test + public void lengthHappyPath() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + final Map elements = new HashMap<>(); + elements.put(1, 1.0); + elements.put(2, 2.0); + elements.put(3, 3.0); + + assertThat(leaderboard.length()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(LengthResponse.Success.class)) + .satisfies(resp -> assertThat(resp.length()).isEqualTo(0)); + + assertThat(leaderboard.upsert(elements)) + .succeedsWithin(FIVE_SECONDS) + .isInstanceOf(UpsertResponse.Success.class); + + assertThat(leaderboard.length()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(LengthResponse.Success.class)) + .satisfies(resp -> assertThat(resp.length()).isEqualTo(3)); + } + + @Test + public void lengthInvalidCacheName() { + final String leaderboardName = randomString("leaderboard"); + //noinspection DataFlowIssue + final ILeaderboard leaderboard = leaderboardClient.leaderboard(null, leaderboardName); + + assertThat(leaderboard.length()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(LengthResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } + + @Test + public void lengthNonExistentCache() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(randomString(), leaderboardName); + + assertThat(leaderboard.length()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(LengthResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(CacheNotFoundException.class)); + } + + @Test + public void lengthInvalidLeaderboardName() { + final String leaderboardName = ""; + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + assertThat(leaderboard.length()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(LengthResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } + // remove elements @Test @@ -286,6 +599,7 @@ public void removeElementsHappyPath() { @Test public void removeElementsInvalidCacheName() { final String leaderboardName = randomString("leaderboard"); + //noinspection DataFlowIssue final ILeaderboard leaderboard = leaderboardClient.leaderboard(null, leaderboardName); final List ids = new ArrayList<>(); @@ -324,4 +638,73 @@ public void removeElementsInvalidLeaderboardName() { .asInstanceOf(InstanceOfAssertFactories.type(RemoveElementsResponse.Error.class)) .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); } + + // delete + + @Test + public void deleteHappyPath() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + final Map elements = new HashMap<>(); + elements.put(1, 1.0); + elements.put(2, 2.0); + elements.put(3, 3.0); + + assertThat(leaderboard.delete()) + .succeedsWithin(FIVE_SECONDS) + .isInstanceOf(DeleteResponse.Success.class); + + assertThat(leaderboard.upsert(elements)) + .succeedsWithin(FIVE_SECONDS) + .isInstanceOf(UpsertResponse.Success.class); + + assertThat(leaderboard.length()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(LengthResponse.Success.class)) + .satisfies(resp -> assertThat(resp.length()).isEqualTo(3)); + + assertThat(leaderboard.delete()) + .succeedsWithin(FIVE_SECONDS) + .isInstanceOf(DeleteResponse.Success.class); + + assertThat(leaderboard.length()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(LengthResponse.Success.class)) + .satisfies(resp -> assertThat(resp.length()).isEqualTo(0)); + } + + @Test + public void deleteInvalidCacheName() { + final String leaderboardName = randomString("leaderboard"); + //noinspection DataFlowIssue + final ILeaderboard leaderboard = leaderboardClient.leaderboard(null, leaderboardName); + + assertThat(leaderboard.delete()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(DeleteResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } + + @Test + public void deleteNonExistentCache() { + final String leaderboardName = randomString("leaderboard"); + final ILeaderboard leaderboard = leaderboardClient.leaderboard(randomString(), leaderboardName); + + assertThat(leaderboard.delete()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(DeleteResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(CacheNotFoundException.class)); + } + + @Test + public void deleteInvalidLeaderboardName() { + final String leaderboardName = ""; + final ILeaderboard leaderboard = leaderboardClient.leaderboard(cacheName, leaderboardName); + + assertThat(leaderboard.delete()) + .succeedsWithin(FIVE_SECONDS) + .asInstanceOf(InstanceOfAssertFactories.type(DeleteResponse.Error.class)) + .satisfies(error -> assertThat(error).hasCauseInstanceOf(InvalidArgumentException.class)); + } } diff --git a/momento-sdk/src/main/java/momento/sdk/ILeaderboard.java b/momento-sdk/src/main/java/momento/sdk/ILeaderboard.java index 8235120b..6f01dd70 100644 --- a/momento-sdk/src/main/java/momento/sdk/ILeaderboard.java +++ b/momento-sdk/src/main/java/momento/sdk/ILeaderboard.java @@ -3,15 +3,18 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import momento.sdk.responses.SortOrder; +import momento.sdk.responses.leaderboard.DeleteResponse; import momento.sdk.responses.leaderboard.FetchResponse; +import momento.sdk.responses.leaderboard.LengthResponse; import momento.sdk.responses.leaderboard.RemoveElementsResponse; import momento.sdk.responses.leaderboard.UpsertResponse; public interface ILeaderboard { /** - * Updates elements in a leaderboard or inserts elements if they do not already exist. The + * Update elements in a leaderboard or insert elements if they do not already exist. The * leaderboard is also created if it does not already exist. Note: can upsert a maximum of 8192 * elements at a time. * @@ -22,7 +25,54 @@ public interface ILeaderboard { CompletableFuture upsert(@Nonnull Map elements); /** - * Fetch the elements in the given leaderboard by index (rank). Note: can fetch a maximum of 8192 + * Fetch the elements of the leaderboard by score. Note: can fetch a maximum of 8192 elements at a + * time. + * + * @param minScore The minimum score (inclusive) of the elements to fetch. Defaults to negative + * infinity. + * @param maxScore The maximum score (exclusive) of the elements to fetch. Defaults to positive + * infinity. + * @param order The order to fetch the elements in. Defaults to {@link SortOrder#ASCENDING}. + * @param offset The number of elements to skip before returning the first element. Defaults to 0. + * Note: this is not the score of the first element to return, but the number of elements of + * the result set to skip before returning the first element. + * @param count The maximum number of elements to return. Defaults to 8192, which is the maximum + * that can be fetched at a time. + * @return A future containing the result of the fetch operation: {@link FetchResponse.Success} + * containing the elements, or {@link FetchResponse.Error}. + */ + CompletableFuture fetchByScore( + @Nullable Double minScore, + @Nullable Double maxScore, + @Nullable SortOrder order, + @Nullable Integer offset, + @Nullable Integer count); + + /** + * Fetch the elements of the leaderboard by score. Note: can fetch a maximum of 8192 elements at a + * time. + * + * @param minScore The minimum score (inclusive) of the elements to fetch. Defaults to negative + * infinity. + * @param maxScore The maximum score (exclusive) of the elements to fetch. Defaults to positive + * infinity. + * @param order The order to fetch the elements in. Defaults to {@link SortOrder#ASCENDING}. + * @return A future containing the result of the fetch operation: {@link FetchResponse.Success} + * containing the elements, or {@link FetchResponse.Error}. + */ + CompletableFuture fetchByScore( + @Nullable Double minScore, @Nullable Double maxScore, @Nullable SortOrder order); + + /** + * Fetch the first 8192 elements of the leaderboard, sorted by score ascending. + * + * @return A future containing the result of the fetch operation: {@link FetchResponse.Success} + * containing the elements, or {@link FetchResponse.Error}. + */ + CompletableFuture fetchByScore(); + + /** + * Fetch the elements of the leaderboard by index (rank). Note: can fetch a maximum of 8192 * elements at a time and rank is 0-based (index begins at 0). * * @param startRank The rank of the first element to fetch. This rank is inclusive, i.e. the @@ -31,20 +81,49 @@ public interface ILeaderboard { * @param endRank The rank of the last element to fetch. This rank is exclusive, i.e. the element * at this rank will not be fetched. Ranks can be used to manually paginate through the * leaderboard in batches of 8192 elements (e.g. request 0-8192, then 8192-16384, etc.). - * @param order The order to fetch the elements in. + * @param order The order to fetch the elements in. Defaults to {@link SortOrder#ASCENDING}. * @return A future containing the result of the fetch operation: {@link FetchResponse.Success} * containing the elements, or {@link FetchResponse.Error}. */ CompletableFuture fetchByRank( - int startRank, int endRank, @Nonnull SortOrder order); + int startRank, int endRank, @Nullable SortOrder order); + + /** + * Look up the rank of the given elements in the leaderboard. The returned elements will be sorted + * numerically by their IDs. Note: rank is 0-based (index begins at 0). + * + * @param ids The IDs of the elements to fetch from the leaderboard. + * @param order The rank order to fetch the elements in, based on their scores. Defaults to {@link + * SortOrder#ASCENDING}. + * @return A future containing the result of the fetch operation: {@link FetchResponse.Success} + * containing the elements, or {@link FetchResponse.Error}. + */ + CompletableFuture getRank( + @Nonnull Iterable ids, @Nullable SortOrder order); /** - * Remove multiple elements from the given leaderboard Note: can remove a maximum of 8192 elements - * at a time. + * Fetch the length (number of items) of the leaderboard. + * + * @return A future containing the result of the length operation: {@link LengthResponse.Success} + * containing the length of the leaderboard, or {@link LengthResponse.Error}. + */ + CompletableFuture length(); + + /** + * Remove multiple elements from the leaderboard. Note: can remove a maximum of 8192 elements at a + * time. * * @param ids The IDs of the elements to remove from the leaderboard. * @return A future containing the result of the remove operation: {@link * RemoveElementsResponse.Success} or {@link RemoveElementsResponse.Error}. */ CompletableFuture removeElements(@Nonnull Iterable ids); + + /** + * Delete all elements in the leaderboard. + * + * @return A future containing the result of the delete operation: {@link DeleteResponse.Success}, + * or {@link DeleteResponse.Error}. + */ + CompletableFuture delete(); } diff --git a/momento-sdk/src/main/java/momento/sdk/Leaderboard.java b/momento-sdk/src/main/java/momento/sdk/Leaderboard.java index 5f1bd5f3..c7a7bd04 100644 --- a/momento-sdk/src/main/java/momento/sdk/Leaderboard.java +++ b/momento-sdk/src/main/java/momento/sdk/Leaderboard.java @@ -3,8 +3,11 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import momento.sdk.responses.SortOrder; +import momento.sdk.responses.leaderboard.DeleteResponse; import momento.sdk.responses.leaderboard.FetchResponse; +import momento.sdk.responses.leaderboard.LengthResponse; import momento.sdk.responses.leaderboard.RemoveElementsResponse; import momento.sdk.responses.leaderboard.UpsertResponse; @@ -25,14 +28,54 @@ public CompletableFuture upsert(@Nonnull Map el return leaderboardDataClient.upsert(cacheName, leaderboardName, elements); } + @Override + public CompletableFuture fetchByScore( + @Nullable Double minScore, + @Nullable Double maxScore, + @Nullable SortOrder order, + @Nullable Integer offset, + @Nullable Integer count) { + return leaderboardDataClient.fetchByScore( + cacheName, leaderboardName, minScore, maxScore, order, offset, count); + } + + @Override + public CompletableFuture fetchByScore( + @Nullable Double minScore, @Nullable Double maxScore, @Nullable SortOrder order) { + return leaderboardDataClient.fetchByScore( + cacheName, leaderboardName, minScore, maxScore, order, null, null); + } + + @Override + public CompletableFuture fetchByScore() { + return leaderboardDataClient.fetchByScore( + cacheName, leaderboardName, null, null, null, null, null); + } + @Override public CompletableFuture fetchByRank( - int startRank, int endRank, @Nonnull SortOrder order) { + int startRank, int endRank, @Nullable SortOrder order) { return leaderboardDataClient.fetchByRank(cacheName, leaderboardName, startRank, endRank, order); } + @Override + public CompletableFuture getRank( + @Nonnull Iterable ids, @Nullable SortOrder order) { + return leaderboardDataClient.getRank(cacheName, leaderboardName, ids, order); + } + + @Override + public CompletableFuture length() { + return leaderboardDataClient.length(cacheName, leaderboardName); + } + @Override public CompletableFuture removeElements(@Nonnull Iterable ids) { return leaderboardDataClient.removeElements(cacheName, leaderboardName, ids); } + + @Override + public CompletableFuture delete() { + return leaderboardDataClient.delete(cacheName, leaderboardName); + } } diff --git a/momento-sdk/src/main/java/momento/sdk/LeaderboardDataClient.java b/momento-sdk/src/main/java/momento/sdk/LeaderboardDataClient.java index 55a290f6..2d2448bf 100644 --- a/momento-sdk/src/main/java/momento/sdk/LeaderboardDataClient.java +++ b/momento-sdk/src/main/java/momento/sdk/LeaderboardDataClient.java @@ -1,18 +1,31 @@ package momento.sdk; import static momento.sdk.ValidationUtils.checkCacheNameValid; +import static momento.sdk.ValidationUtils.checkScoreRangeValid; +import static momento.sdk.ValidationUtils.validateCount; import static momento.sdk.ValidationUtils.validateLeaderboardElements; import static momento.sdk.ValidationUtils.validateLeaderboardName; +import static momento.sdk.ValidationUtils.validateNotNull; +import static momento.sdk.ValidationUtils.validateOffset; import static momento.sdk.ValidationUtils.validateRankRange; import com.google.common.util.concurrent.ListenableFuture; import grpc.common._Empty; +import grpc.leaderboard._DeleteLeaderboardRequest; import grpc.leaderboard._Element; import grpc.leaderboard._GetByRankRequest; import grpc.leaderboard._GetByRankResponse; +import grpc.leaderboard._GetByScoreRequest; +import grpc.leaderboard._GetByScoreResponse; +import grpc.leaderboard._GetLeaderboardLengthRequest; +import grpc.leaderboard._GetLeaderboardLengthResponse; +import grpc.leaderboard._GetRankRequest; +import grpc.leaderboard._GetRankResponse; import grpc.leaderboard._Order; import grpc.leaderboard._RankRange; +import grpc.leaderboard._RankedElement; import grpc.leaderboard._RemoveElementsRequest; +import grpc.leaderboard._ScoreRange; import grpc.leaderboard._UpsertElementsRequest; import io.grpc.Metadata; import java.util.List; @@ -22,12 +35,15 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import momento.sdk.auth.CredentialProvider; import momento.sdk.config.LeaderboardConfiguration; import momento.sdk.exceptions.CacheServiceExceptionMapper; import momento.sdk.responses.SortOrder; +import momento.sdk.responses.leaderboard.DeleteResponse; import momento.sdk.responses.leaderboard.FetchResponse; import momento.sdk.responses.leaderboard.LeaderboardElement; +import momento.sdk.responses.leaderboard.LengthResponse; import momento.sdk.responses.leaderboard.RemoveElementsResponse; import momento.sdk.responses.leaderboard.UpsertResponse; @@ -59,12 +75,34 @@ public CompletableFuture upsert( } } + public CompletableFuture fetchByScore( + @Nonnull String cacheName, + @Nonnull String leaderboardName, + @Nullable Double minScore, + @Nullable Double maxScore, + @Nullable SortOrder order, + @Nullable Integer offset, + @Nullable Integer count) { + try { + checkCacheNameValid(cacheName); + validateLeaderboardName(leaderboardName); + checkScoreRangeValid(minScore, maxScore); + validateOffset(offset); + validateCount(count); + + return sendFetchByScore(cacheName, leaderboardName, minScore, maxScore, order, offset, count); + } catch (Exception e) { + return CompletableFuture.completedFuture( + new FetchResponse.Error(CacheServiceExceptionMapper.convert(e))); + } + } + public CompletableFuture fetchByRank( @Nonnull String cacheName, @Nonnull String leaderboardName, int startRank, int endRank, - @Nonnull SortOrder order) { + @Nullable SortOrder order) { try { checkCacheNameValid(cacheName); validateLeaderboardName(leaderboardName); @@ -77,11 +115,42 @@ public CompletableFuture fetchByRank( } } + public CompletableFuture getRank( + @Nonnull String cacheName, + @Nonnull String leaderboardName, + @Nonnull Iterable ids, + @Nullable SortOrder order) { + try { + checkCacheNameValid(cacheName); + validateLeaderboardName(leaderboardName); + validateNotNull(ids, "ids"); + + return sendGetRank(cacheName, leaderboardName, ids, order); + } catch (Exception e) { + return CompletableFuture.completedFuture( + new FetchResponse.Error(CacheServiceExceptionMapper.convert(e))); + } + } + + public CompletableFuture length( + @Nonnull String cacheName, @Nonnull String leaderboardName) { + try { + checkCacheNameValid(cacheName); + validateLeaderboardName(leaderboardName); + + return sendLength(cacheName, leaderboardName); + } catch (Exception e) { + return CompletableFuture.completedFuture( + new LengthResponse.Error(CacheServiceExceptionMapper.convert(e))); + } + } + public CompletableFuture removeElements( @Nonnull String cacheName, @Nonnull String leaderboardName, @Nonnull Iterable ids) { try { checkCacheNameValid(cacheName); validateLeaderboardName(leaderboardName); + validateNotNull(ids, "ids"); return sendRemoveElements(cacheName, leaderboardName, ids); } catch (Exception e) { @@ -90,6 +159,19 @@ public CompletableFuture removeElements( } } + public CompletableFuture delete( + @Nonnull String cacheName, @Nonnull String leaderboardName) { + try { + checkCacheNameValid(cacheName); + validateLeaderboardName(leaderboardName); + + return sendDelete(cacheName, leaderboardName); + } catch (Exception e) { + return CompletableFuture.completedFuture( + new DeleteResponse.Error(CacheServiceExceptionMapper.convert(e))); + } + } + private CompletableFuture sendUpsert( @Nonnull String cacheName, @Nonnull String leaderboardName, @@ -108,12 +190,37 @@ private CompletableFuture sendUpsert( return executeGrpcFunction(stubSupplier, success, failure); } + CompletableFuture sendFetchByScore( + @Nonnull String cacheName, + @Nonnull String leaderboardName, + @Nullable Double minScore, + @Nullable Double maxScore, + @Nullable SortOrder order, + @Nullable Integer offset, + @Nullable Integer count) { + final Metadata metadata = metadataWithCache(cacheName); + final Supplier> stubSupplier = + () -> + attachMetadata(stubsManager.getStub(), metadata) + .getByScore( + buildGetByScoreRequest( + cacheName, leaderboardName, minScore, maxScore, order, offset, count)); + + final Function<_GetByScoreResponse, FetchResponse> success = + rsp -> new FetchResponse.Success(convertToLeaderboardElements(rsp.getElementsList())); + + final Function failure = + e -> new FetchResponse.Error(CacheServiceExceptionMapper.convert(e)); + + return executeGrpcFunction(stubSupplier, success, failure); + } + CompletableFuture sendFetchByRank( @Nonnull String cacheName, @Nonnull String leaderboardName, int startRank, int endRank, - @Nonnull SortOrder order) { + @Nullable SortOrder order) { final Metadata metadata = metadataWithCache(cacheName); final Supplier> stubSupplier = () -> @@ -122,13 +229,7 @@ CompletableFuture sendFetchByRank( buildGetByRankRequest(cacheName, leaderboardName, startRank, endRank, order)); final Function<_GetByRankResponse, FetchResponse> success = - rsp -> { - final List elements = - rsp.getElementsList().stream() - .map(e -> new LeaderboardElement(e.getId(), e.getScore(), e.getRank())) - .collect(Collectors.toList()); - return new FetchResponse.Success(elements); - }; + rsp -> new FetchResponse.Success(convertToLeaderboardElements(rsp.getElementsList())); final Function failure = e -> new FetchResponse.Error(CacheServiceExceptionMapper.convert(e)); @@ -136,6 +237,52 @@ CompletableFuture sendFetchByRank( return executeGrpcFunction(stubSupplier, success, failure); } + CompletableFuture sendGetRank( + @Nonnull String cacheName, + @Nonnull String leaderboardName, + @Nonnull Iterable ids, + @Nullable SortOrder order) { + final Metadata metadata = metadataWithCache(cacheName); + final Supplier> stubSupplier = + () -> + attachMetadata(stubsManager.getStub(), metadata) + .getRank(buildGetRankRequest(cacheName, leaderboardName, ids, order)); + + final Function<_GetRankResponse, FetchResponse> success = + rsp -> new FetchResponse.Success(convertToLeaderboardElements(rsp.getElementsList())); + + final Function failure = + e -> new FetchResponse.Error(CacheServiceExceptionMapper.convert(e)); + + return executeGrpcFunction(stubSupplier, success, failure); + } + + private List convertToLeaderboardElements(List<_RankedElement> elements) { + return elements.stream() + .map(e -> new LeaderboardElement(e.getId(), e.getScore(), e.getRank())) + .collect(Collectors.toList()); + } + + private CompletableFuture sendLength( + @Nonnull String cacheName, @Nonnull String leaderboardName) { + final Metadata metadata = metadataWithCache(cacheName); + final Supplier> stubSupplier = + () -> + attachMetadata(stubsManager.getStub(), metadata) + .getLeaderboardLength(buildLengthRequest(cacheName, leaderboardName)); + + final Function<_GetLeaderboardLengthResponse, LengthResponse> success = + rsp -> { + final int length = rsp.getCount(); + return new LengthResponse.Success(length); + }; + + final Function failure = + e -> new LengthResponse.Error(CacheServiceExceptionMapper.convert(e)); + + return executeGrpcFunction(stubSupplier, success, failure); + } + private CompletableFuture sendRemoveElements( @Nonnull String cacheName, @Nonnull String leaderboardName, @Nonnull Iterable ids) { final Metadata metadata = metadataWithCache(cacheName); @@ -153,6 +300,22 @@ private CompletableFuture sendRemoveElements( return executeGrpcFunction(stubSupplier, success, failure); } + private CompletableFuture sendDelete( + @Nonnull String cacheName, @Nonnull String leaderboardName) { + final Metadata metadata = metadataWithCache(cacheName); + final Supplier> stubSupplier = + () -> + attachMetadata(stubsManager.getStub(), metadata) + .deleteLeaderboard(buildDeleteRequest(cacheName, leaderboardName)); + + final Function<_Empty, DeleteResponse> success = rsp -> new DeleteResponse.Success(); + + final Function failure = + e -> new DeleteResponse.Error(CacheServiceExceptionMapper.convert(e)); + + return executeGrpcFunction(stubSupplier, success, failure); + } + private _UpsertElementsRequest buildUpsertElementsRequest( @Nonnull String cacheName, @Nonnull String leaderboardName, @@ -167,18 +330,102 @@ private _UpsertElementsRequest buildUpsertElementsRequest( .build(); } + private _GetByScoreRequest buildGetByScoreRequest( + @Nonnull String cacheName, + @Nonnull String leaderboardName, + @Nullable Double minScore, + @Nullable Double maxScore, + @Nullable SortOrder order, + @Nullable Integer offset, + @Nullable Integer count) { + final _ScoreRange.Builder scoreBuilder = _ScoreRange.newBuilder(); + if (minScore != null) { + scoreBuilder.setMinInclusive(minScore); + } else { + scoreBuilder.setUnboundedMin(scoreBuilder.getUnboundedMin()); + } + if (maxScore != null) { + scoreBuilder.setMaxExclusive(maxScore); + } else { + scoreBuilder.setUnboundedMax(scoreBuilder.getUnboundedMax()); + } + + final _GetByScoreRequest.Builder requestBuilder = + _GetByScoreRequest.newBuilder() + .setCacheName(cacheName) + .setLeaderboard(leaderboardName) + .setScoreRange(scoreBuilder.build()); + + if (order == SortOrder.DESCENDING) { + requestBuilder.setOrder(_Order.DESCENDING); + } else { + requestBuilder.setOrder(_Order.ASCENDING); + } + + if (offset != null) { + requestBuilder.setOffset(offset); + } else { + requestBuilder.setOffset(0); + } + + if (count != null) { + requestBuilder.setLimitElements(count); + } else { + requestBuilder.setLimitElements(8192); + } + + return requestBuilder.build(); + } + private _GetByRankRequest buildGetByRankRequest( @Nonnull String cacheName, @Nonnull String leaderboardName, int startRank, int endRank, - @Nonnull SortOrder order) { - return _GetByRankRequest.newBuilder() + @Nullable SortOrder order) { + final _GetByRankRequest.Builder requestBuilder = + _GetByRankRequest.newBuilder() + .setCacheName(cacheName) + .setLeaderboard(leaderboardName) + .setRankRange( + _RankRange.newBuilder() + .setStartInclusive(startRank) + .setEndExclusive(endRank) + .build()); + + if (order == SortOrder.DESCENDING) { + requestBuilder.setOrder(_Order.DESCENDING); + } else { + requestBuilder.setOrder(_Order.ASCENDING); + } + return requestBuilder.build(); + } + + private _GetRankRequest buildGetRankRequest( + @Nonnull String cacheName, + @Nonnull String leaderboardName, + @Nonnull Iterable ids, + @Nullable SortOrder order) { + final _GetRankRequest.Builder requestBuilder = + _GetRankRequest.newBuilder() + .setCacheName(cacheName) + .setLeaderboard(leaderboardName) + .addAllIds(ids); + + if (order == SortOrder.DESCENDING) { + requestBuilder.setOrder(_Order.DESCENDING); + } else { + requestBuilder.setOrder(_Order.ASCENDING); + } + + return requestBuilder.build(); + } + + private _GetLeaderboardLengthRequest buildLengthRequest( + @Nonnull String cacheName, @Nonnull String leaderboardName) { + return _GetLeaderboardLengthRequest.newBuilder() .setCacheName(cacheName) .setLeaderboard(leaderboardName) - .setRankRange( - _RankRange.newBuilder().setStartInclusive(startRank).setEndExclusive(endRank).build()) - .setOrder(order == SortOrder.ASCENDING ? _Order.ASCENDING : _Order.DESCENDING) .build(); } @@ -191,6 +438,14 @@ private _RemoveElementsRequest buildRemoveElementsRequest( .build(); } + private _DeleteLeaderboardRequest buildDeleteRequest( + @Nonnull String cacheName, @Nonnull String leaderboardName) { + return _DeleteLeaderboardRequest.newBuilder() + .setCacheName(cacheName) + .setLeaderboard(leaderboardName) + .build(); + } + @Override public void doClose() { stubsManager.close(); diff --git a/momento-sdk/src/main/java/momento/sdk/ScsDataClient.java b/momento-sdk/src/main/java/momento/sdk/ScsDataClient.java index 3e799b5c..ee283a4e 100644 --- a/momento-sdk/src/main/java/momento/sdk/ScsDataClient.java +++ b/momento-sdk/src/main/java/momento/sdk/ScsDataClient.java @@ -6,14 +6,14 @@ import static momento.sdk.ValidationUtils.checkListNameValid; import static momento.sdk.ValidationUtils.checkScoreRangeValid; import static momento.sdk.ValidationUtils.checkSetNameValid; -import static momento.sdk.ValidationUtils.checkSortedSetCountValid; import static momento.sdk.ValidationUtils.checkSortedSetNameValid; -import static momento.sdk.ValidationUtils.checkSortedSetOffsetValid; import static momento.sdk.ValidationUtils.ensureValidCacheSet; import static momento.sdk.ValidationUtils.ensureValidKey; import static momento.sdk.ValidationUtils.ensureValidTruncateToSize; import static momento.sdk.ValidationUtils.ensureValidTtl; import static momento.sdk.ValidationUtils.ensureValidValue; +import static momento.sdk.ValidationUtils.validateCount; +import static momento.sdk.ValidationUtils.validateOffset; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -734,8 +734,8 @@ CompletableFuture sortedSetFetchByScore( checkCacheNameValid(cacheName); checkSortedSetNameValid(sortedSetName); checkScoreRangeValid(minScore, maxScore); - checkSortedSetOffsetValid(offset); - checkSortedSetCountValid(count); + validateOffset(offset); + validateCount(count); return sendSortedSetFetchByScore( cacheName, convert(sortedSetName), minScore, maxScore, order, offset, count); diff --git a/momento-sdk/src/main/java/momento/sdk/ValidationUtils.java b/momento-sdk/src/main/java/momento/sdk/ValidationUtils.java index a1a1f040..fd7a2f3a 100644 --- a/momento-sdk/src/main/java/momento/sdk/ValidationUtils.java +++ b/momento-sdk/src/main/java/momento/sdk/ValidationUtils.java @@ -14,15 +14,7 @@ public final class ValidationUtils { static final String REQUEST_DEADLINE_MUST_BE_POSITIVE = "Request deadline must be positive"; static final String CACHE_ITEM_TTL_CANNOT_BE_NEGATIVE = "Cache item TTL cannot be negative."; - static final String A_NON_NULL_KEY_IS_REQUIRED = "A non-null key is required."; - static final String A_NON_NULL_VALUE_IS_REQUIRED = "A non-null value is required."; - static final String CACHE_NAME_IS_REQUIRED = "Cache name is required."; - static final String STORE_NAME_IS_REQUIRED = "Store name is required."; - static final String TOPIC_NAME_IS_REQUIRED = "Topic name is required."; - static final String DICTIONARY_NAME_IS_REQUIRED = "Dictionary name is required."; - static final String SET_NAME_CANNOT_BE_NULL = "Set name cannot be null."; - static final String SORTED_SET_NAME_CANNOT_BE_NULL = "Sorted set name cannot be null."; - static final String LIST_NAME_CANNOT_BE_NULL = "List name cannot be null."; + static final String CANNOT_BE_NULL = " cannot be null."; static final String INDEX_RANGE_INVALID = "endIndex (exclusive) must be larger than startIndex (inclusive)."; static final String SCORE_RANGE_INVALID = @@ -55,51 +47,35 @@ public static void ensureRequestDeadlineValid(Duration requestDeadline) { } static void checkCacheNameValid(String cacheName) { - if (cacheName == null) { - throw new InvalidArgumentException(CACHE_NAME_IS_REQUIRED); - } + validateNotNull(cacheName, "Cache name"); } static void checkStoreNameValid(String storeName) { - if (storeName == null) { - throw new InvalidArgumentException(STORE_NAME_IS_REQUIRED); - } + validateNotNull(storeName, "Store name"); } static void checkTopicNameValid(String topicName) { - if (topicName == null) { - throw new InvalidArgumentException(TOPIC_NAME_IS_REQUIRED); - } + validateNotNull(topicName, "Topic name"); } static void checkDictionaryNameValid(String dictionaryName) { - if (dictionaryName == null) { - throw new InvalidArgumentException(DICTIONARY_NAME_IS_REQUIRED); - } + validateNotNull(dictionaryName, "Dictionary name"); } static void checkListNameValid(byte[] listName) { - if (listName == null) { - throw new InvalidArgumentException(LIST_NAME_CANNOT_BE_NULL); - } + validateNotNull(listName, "List name"); } static void checkListNameValid(String listName) { - if (listName == null) { - throw new InvalidArgumentException(LIST_NAME_CANNOT_BE_NULL); - } + validateNotNull(listName, "List name"); } static void checkSetNameValid(String setName) { - if (setName == null) { - throw new InvalidArgumentException(SET_NAME_CANNOT_BE_NULL); - } + validateNotNull(setName, "Set name"); } static void checkSortedSetNameValid(String sortedSetName) { - if (sortedSetName == null) { - throw new InvalidArgumentException(SORTED_SET_NAME_CANNOT_BE_NULL); - } + validateNotNull(sortedSetName, "Sorted set name"); } static void checkIndexRangeValid(Integer startIndex, Integer endIndex) { @@ -118,13 +94,13 @@ static void checkScoreRangeValid(Double minScore, Double maxScore) { } } - static void checkSortedSetOffsetValid(Integer offset) { + static void validateOffset(Integer offset) { if (offset != null && offset < 0) { throw new InvalidArgumentException("Offset must be greater than or equal to 0."); } } - static void checkSortedSetCountValid(Integer count) { + static void validateCount(Integer count) { if (count != null && count <= 0) { throw new InvalidArgumentException("Count must be greater than 0."); } @@ -137,15 +113,11 @@ static void ensureValidCacheSet(Object key, Object value, Duration ttl) { } static void ensureValidKey(Object key) { - if (key == null) { - throw new InvalidArgumentException(A_NON_NULL_KEY_IS_REQUIRED); - } + validateNotNull(key, "Key"); } static void ensureValidValue(Object value) { - if (value == null) { - throw new InvalidArgumentException(A_NON_NULL_VALUE_IS_REQUIRED); - } + validateNotNull(value, "Value"); } static void ensureValidTtl(Duration ttl) { @@ -193,4 +165,10 @@ static void validateRankRange(int startRank, int endRank) { throw new InvalidArgumentException(RANK_RANGE_INVALID); } } + + static void validateNotNull(Object object, String name) { + if (object == null) { + throw new InvalidArgumentException(name + CANNOT_BE_NULL); + } + } } diff --git a/momento-sdk/src/main/java/momento/sdk/config/transport/leaderboard/LeaderboardGrpcConfiguration.java b/momento-sdk/src/main/java/momento/sdk/config/transport/leaderboard/LeaderboardGrpcConfiguration.java index 7a4653fe..d670cc43 100644 --- a/momento-sdk/src/main/java/momento/sdk/config/transport/leaderboard/LeaderboardGrpcConfiguration.java +++ b/momento-sdk/src/main/java/momento/sdk/config/transport/leaderboard/LeaderboardGrpcConfiguration.java @@ -28,7 +28,7 @@ public LeaderboardGrpcConfiguration(@Nonnull Duration deadline) { this( deadline, 1, - GrpcChannelOptions.DEFAULT_MAX_MESSAGE_SIZE, + GrpcChannelOptions.DEFAULT_LEADERBOARD_MAX_MESSAGE_SIZE, GrpcChannelOptions.DEFAULT_KEEPALIVE_WITHOUT_STREAM, GrpcChannelOptions.DEFAULT_KEEPALIVE_TIMEOUT, GrpcChannelOptions.DEFAULT_KEEPALIVE_TIME); diff --git a/momento-sdk/src/main/java/momento/sdk/internal/GrpcChannelOptions.java b/momento-sdk/src/main/java/momento/sdk/internal/GrpcChannelOptions.java index e18fde0b..9d6e95a3 100644 --- a/momento-sdk/src/main/java/momento/sdk/internal/GrpcChannelOptions.java +++ b/momento-sdk/src/main/java/momento/sdk/internal/GrpcChannelOptions.java @@ -6,10 +6,13 @@ import momento.sdk.config.transport.IGrpcConfiguration; public class GrpcChannelOptions { - // The default value for max_send_message_length is 4mb. We need to increase this to 5mb in order - // to - // support cases where users have requested a limit increase up to our maximum item size of 5mb. + // The default value for max_send_message_length is 4MB. We need to increase this to 5MB in order + // to support cases where users have requested a limit increase up to our maximum item size of + // 5MB. public static final int DEFAULT_MAX_MESSAGE_SIZE = 5_243_000; // bytes + // Leaderboards have a separate max message size from the cache methods. This default limit of + // 200MB is in place to prevent memory issues in the event of an erroneously large message. + public static final int DEFAULT_LEADERBOARD_MAX_MESSAGE_SIZE = 209_715_200; // bytes public static final boolean DEFAULT_KEEPALIVE_WITHOUT_STREAM = true; public static final int DEFAULT_KEEPALIVE_TIME_MS = 5000; // milliseconds diff --git a/momento-sdk/src/main/java/momento/sdk/responses/leaderboard/DeleteResponse.java b/momento-sdk/src/main/java/momento/sdk/responses/leaderboard/DeleteResponse.java new file mode 100644 index 00000000..fa8d4aed --- /dev/null +++ b/momento-sdk/src/main/java/momento/sdk/responses/leaderboard/DeleteResponse.java @@ -0,0 +1,38 @@ +package momento.sdk.responses.leaderboard; + +import momento.sdk.exceptions.SdkException; +import momento.sdk.internal.StringHelpers; + +/** Response for a leaderboard delete operation */ +public interface DeleteResponse { + + /** A successful delete operation. */ + class Success implements DeleteResponse { + @Override + public String toString() { + return StringHelpers.emptyToString("DeleteResponse.Success"); + } + } + + /** + * A failed delete operation. The response itself is an exception, so it can be directly thrown, + * or the cause of the error can be retrieved with {@link #getCause()}. The message is a copy of + * the message of the cause. + */ + class Error extends SdkException implements DeleteResponse { + + /** + * Constructs a leaderboard delete error with a cause. + * + * @param cause the cause. + */ + public Error(SdkException cause) { + super(cause); + } + + @Override + public String toString() { + return buildToString("DeleteResponse.Error"); + } + } +} diff --git a/momento-sdk/src/main/java/momento/sdk/responses/leaderboard/LengthResponse.java b/momento-sdk/src/main/java/momento/sdk/responses/leaderboard/LengthResponse.java new file mode 100644 index 00000000..797d7d26 --- /dev/null +++ b/momento-sdk/src/main/java/momento/sdk/responses/leaderboard/LengthResponse.java @@ -0,0 +1,53 @@ +package momento.sdk.responses.leaderboard; + +import momento.sdk.exceptions.SdkException; +import momento.sdk.internal.StringHelpers; + +/** Response for a leaderboard length operation */ +public interface LengthResponse { + + /** A successful length operation. */ + class Success implements LengthResponse { + private final int length; + + public Success(int length) { + this.length = length; + } + + /** + * Gets the length of the leaderboard. + * + * @return the length. + */ + public int length() { + return length; + } + + @Override + public String toString() { + return StringHelpers.emptyToString("LengthResponse.Success") + ": length: " + length; + } + } + + /** + * A failed length operation. The response itself is an exception, so it can be directly thrown, + * or the cause of the error can be retrieved with {@link #getCause()}. The message is a copy of + * the message of the cause. + */ + class Error extends SdkException implements LengthResponse { + + /** + * Constructs a leaderboard length error with a cause. + * + * @param cause the cause. + */ + public Error(SdkException cause) { + super(cause); + } + + @Override + public String toString() { + return buildToString("LengthResponse.Error"); + } + } +}