Skip to content

Commit 6b02d16

Browse files
Java: Add Zrank and Zrankwithscores command. (Sorted Set Commands) (valkey-io#1179)
* Java: Add Zrank and Zrankwithscores command. (Sorted Set Commands) (#154)
1 parent 34341ef commit 6b02d16

File tree

7 files changed

+193
-0
lines changed

7 files changed

+193
-0
lines changed

java/client/src/main/java/glide/api/BaseClient.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import static redis_request.RedisRequestOuterClass.RequestType.Zadd;
5454
import static redis_request.RedisRequestOuterClass.RequestType.Zcard;
5555
import static redis_request.RedisRequestOuterClass.RequestType.Zrange;
56+
import static redis_request.RedisRequestOuterClass.RequestType.Zrank;
5657
import static redis_request.RedisRequestOuterClass.RequestType.Zrem;
5758

5859
import glide.api.commands.GenericBaseCommands;
@@ -223,6 +224,10 @@ protected Long handleLongResponse(Response response) throws RedisException {
223224
return handleRedisResponse(Long.class, false, response);
224225
}
225226

227+
protected Long handleLongOrNullResponse(Response response) throws RedisException {
228+
return handleRedisResponse(Long.class, true, response);
229+
}
230+
226231
protected Double handleDoubleResponse(Response response) throws RedisException {
227232
return handleRedisResponse(Double.class, false, response);
228233
}
@@ -647,6 +652,18 @@ public CompletableFuture<Double> zscore(@NonNull String key, @NonNull String mem
647652
ZScore, new String[] {key, member}, this::handleDoubleOrNullResponse);
648653
}
649654

655+
@Override
656+
public CompletableFuture<Long> zrank(@NonNull String key, @NonNull String member) {
657+
return commandManager.submitNewCommand(
658+
Zrank, new String[] {key, member}, this::handleLongOrNullResponse);
659+
}
660+
661+
@Override
662+
public CompletableFuture<Object[]> zrankWithScore(@NonNull String key, @NonNull String member) {
663+
return commandManager.submitNewCommand(
664+
Zrank, new String[] {key, member, WITH_SCORE_REDIS_API}, this::handleArrayOrNullResponse);
665+
}
666+
650667
@Override
651668
public CompletableFuture<Long> pttl(@NonNull String key) {
652669
return commandManager.submitNewCommand(PTTL, new String[] {key}, this::handleLongResponse);

java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
public interface SortedSetBaseCommands {
2020
public static final String WITH_SCORES_REDIS_API = "WITHSCORES";
21+
public static final String WITH_SCORE_REDIS_API = "WITHSCORE";
2122

2223
/**
2324
* Adds members with their scores to the sorted set stored at <code>key</code>.<br>
@@ -411,4 +412,48 @@ CompletableFuture<Map<String, Double>> zrangeWithScores(
411412
* }</pre>
412413
*/
413414
CompletableFuture<Map<String, Double>> zrangeWithScores(String key, ScoredRangeQuery rangeQuery);
415+
416+
/**
417+
* Returns the rank of <code>member</code> in the sorted set stored at <code>key</code>, with
418+
* scores ordered from low to high.<br>
419+
* To get the rank of <code>member</code> with it's score, see <code>zrankWithScore</code>.
420+
*
421+
* @see <a href="https://redis.io/commands/zrank/">redis.io</a> for more details.
422+
* @param key The key of the sorted set.
423+
* @param member The member whose rank is to be retrieved.
424+
* @return The rank of <code>member</code> in the sorted set.<br>
425+
* If <code>key</code> doesn't exist, or if <code>member</code> is not present in the set,
426+
* <code>null</code> will be returned.
427+
* @example
428+
* <pre>{@code
429+
* Long num1 = client.zrank("mySortedSet", "member2").get();
430+
* assert num1 == 3L; // Indicates that "member2" has the second-lowest score in the sorted set "mySortedSet".
431+
*
432+
* Long num2 = client.zcard("mySortedSet", "nonExistingMember").get();
433+
* assert num2 == null; // Indicates that "nonExistingMember" is not present in the sorted set "mySortedSet".
434+
* }</pre>
435+
*/
436+
CompletableFuture<Long> zrank(String key, String member);
437+
438+
/**
439+
* Returns the rank of <code>member</code> in the sorted set stored at <code>key</code> with it's
440+
* score, where scores are ordered from the lowest to highest.
441+
*
442+
* @see <a href="https://redis.io/commands/zrank/">redis.io</a> for more details.
443+
* @param key The key of the sorted set.
444+
* @param member The member whose rank is to be retrieved.
445+
* @return An array containing the rank (as <code>Long</code>) and score (as <code>Double</code>)
446+
* of <code>member</code> in the sorted set.<br>
447+
* If <code>key</code> doesn't exist, or if <code>member</code> is not present in the set,
448+
* <code>null</code> will be returned.
449+
* @example
450+
* <pre>{@code
451+
* Object[] result1 = client.zrankWithScore("mySortedSet", "member2").get();
452+
* assert ((Long)result1[0]) == 1L && ((Double)result1[1]) == 6.0; // Indicates that "member2" with score 6.0 has the second-lowest score in the sorted set "mySortedSet".
453+
*
454+
* Object[] result2 = client.zrankWithScore("mySortedSet", "nonExistingMember").get();
455+
* assert num2 == null; // Indicates that "nonExistingMember" is not present in the sorted set "mySortedSet".
456+
* }</pre>
457+
*/
458+
CompletableFuture<Object[]> zrankWithScore(String key, String member);
414459
}

java/client/src/main/java/glide/api/models/BaseTransaction.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
22
package glide.api.models;
33

4+
import static glide.api.commands.SortedSetBaseCommands.WITH_SCORE_REDIS_API;
45
import static glide.api.models.commands.RangeOptions.createZrangeArgs;
56
import static glide.utils.ArrayTransformUtils.concatenateArrays;
67
import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray;
@@ -63,6 +64,7 @@
6364
import static redis_request.RedisRequestOuterClass.RequestType.Zadd;
6465
import static redis_request.RedisRequestOuterClass.RequestType.Zcard;
6566
import static redis_request.RedisRequestOuterClass.RequestType.Zrange;
67+
import static redis_request.RedisRequestOuterClass.RequestType.Zrank;
6668
import static redis_request.RedisRequestOuterClass.RequestType.Zrem;
6769

6870
import glide.api.models.commands.ExpireOptions;
@@ -1372,6 +1374,42 @@ public T zscore(@NonNull String key, @NonNull String member) {
13721374
return getThis();
13731375
}
13741376

1377+
/**
1378+
* Returns the rank of <code>member</code> in the sorted set stored at <code>key</code>, with
1379+
* scores ordered from low to high.<br>
1380+
* To get the rank of <code>member</code> with it's score, see <code>zrankWithScore</code>.
1381+
*
1382+
* @see <a href="https://redis.io/commands/zrank/">redis.io</a> for more details.
1383+
* @param key The key of the sorted set.
1384+
* @param member The member whose rank is to be retrieved.
1385+
* @return The rank of <code>member</code> in the sorted set.<br>
1386+
* If <code>key</code> doesn't exist, or if <code>member</code> is not present in the set,
1387+
* <code>null</code> will be returned.
1388+
*/
1389+
public T zrank(@NonNull String key, @NonNull String member) {
1390+
ArgsArray commandArgs = buildArgs(new String[] {key, member});
1391+
protobufTransaction.addCommands(buildCommand(Zrank, commandArgs));
1392+
return getThis();
1393+
}
1394+
1395+
/**
1396+
* Returns the rank of <code>member</code> in the sorted set stored at <code>key</code> with it's
1397+
* score, where scores are ordered from the lowest to highest.
1398+
*
1399+
* @see <a href="https://redis.io/commands/zrank/">redis.io</a> for more details.
1400+
* @param key The key of the sorted set.
1401+
* @param member The member whose rank is to be retrieved.
1402+
* @return An array containing the rank (as <code>Long</code>) and score (as <code>Double</code>)
1403+
* of <code>member</code> in the sorted set.<br>
1404+
* If <code>key</code> doesn't exist, or if <code>member</code> is not present in the set,
1405+
* <code>null</code> will be returned.
1406+
*/
1407+
public T zrankWithScore(@NonNull String key, @NonNull String member) {
1408+
ArgsArray commandArgs = buildArgs(new String[] {key, member, WITH_SCORE_REDIS_API});
1409+
protobufTransaction.addCommands(buildCommand(Zrank, commandArgs));
1410+
return getThis();
1411+
}
1412+
13751413
/**
13761414
* Returns the remaining time to live of <code>key</code> that has a timeout, in milliseconds.
13771415
*

java/client/src/test/java/glide/api/RedisClientTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import static glide.api.BaseClient.OK;
55
import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_REDIS_API;
6+
import static glide.api.commands.SortedSetBaseCommands.WITH_SCORE_REDIS_API;
67
import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST;
78
import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_EXISTS;
89
import static glide.api.models.commands.SetOptions.RETURN_OLD_VALUE;
@@ -75,6 +76,7 @@
7576
import static redis_request.RedisRequestOuterClass.RequestType.Zadd;
7677
import static redis_request.RedisRequestOuterClass.RequestType.Zcard;
7778
import static redis_request.RedisRequestOuterClass.RequestType.Zrange;
79+
import static redis_request.RedisRequestOuterClass.RequestType.Zrank;
7880
import static redis_request.RedisRequestOuterClass.RequestType.Zrem;
7981

8082
import glide.api.models.Script;
@@ -2069,6 +2071,56 @@ public void zrangeWithScores_by_score_returns_success() {
20692071
assertEquals(value, payload);
20702072
}
20712073

2074+
@SneakyThrows
2075+
@Test
2076+
public void zrank_returns_success() {
2077+
// setup
2078+
String key = "testKey";
2079+
String member = "testMember";
2080+
String[] arguments = new String[] {key, member};
2081+
Long value = 3L;
2082+
2083+
CompletableFuture<Long> testResponse = new CompletableFuture<>();
2084+
testResponse.complete(value);
2085+
2086+
// match on protobuf request
2087+
when(commandManager.<Long>submitNewCommand(eq(Zrank), eq(arguments), any()))
2088+
.thenReturn(testResponse);
2089+
2090+
// exercise
2091+
CompletableFuture<Long> response = service.zrank(key, member);
2092+
Long payload = response.get();
2093+
2094+
// verify
2095+
assertEquals(testResponse, response);
2096+
assertEquals(value, payload);
2097+
}
2098+
2099+
@SneakyThrows
2100+
@Test
2101+
public void zrankWithScore_returns_success() {
2102+
// setup
2103+
String key = "testKey";
2104+
String member = "testMember";
2105+
String[] arguments = new String[] {key, member, WITH_SCORE_REDIS_API};
2106+
Object[] value = new Object[] {1, 6.0};
2107+
2108+
CompletableFuture<Object[]> testResponse = new CompletableFuture<>();
2109+
testResponse.complete(value);
2110+
2111+
// match on protobuf request
2112+
when(commandManager.<Object[]>submitNewCommand(eq(Zrank), eq(arguments), any()))
2113+
.thenReturn(testResponse);
2114+
2115+
// exercise
2116+
CompletableFuture<Object[]> response = service.zrankWithScore(key, member);
2117+
Object[] payload = response.get();
2118+
2119+
// verify
2120+
assertEquals(testResponse, response);
2121+
assertEquals(value, payload);
2122+
}
2123+
20722124
@SneakyThrows
20732125
@Test
20742126
public void type_returns_success() {

java/client/src/test/java/glide/api/models/TransactionTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package glide.api.models;
33

44
import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_REDIS_API;
5+
import static glide.api.commands.SortedSetBaseCommands.WITH_SCORE_REDIS_API;
56
import static glide.api.models.commands.SetOptions.RETURN_OLD_VALUE;
67
import static org.junit.jupiter.api.Assertions.assertEquals;
78
import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName;
@@ -61,6 +62,7 @@
6162
import static redis_request.RedisRequestOuterClass.RequestType.Zadd;
6263
import static redis_request.RedisRequestOuterClass.RequestType.Zcard;
6364
import static redis_request.RedisRequestOuterClass.RequestType.Zrange;
65+
import static redis_request.RedisRequestOuterClass.RequestType.Zrank;
6466
import static redis_request.RedisRequestOuterClass.RequestType.Zrem;
6567

6668
import glide.api.models.commands.ExpireOptions;
@@ -411,6 +413,19 @@ public void transaction_builds_protobuf_request(BaseTransaction<?> transaction)
411413
transaction.zscore("key", "member");
412414
results.add(Pair.of(ZScore, ArgsArray.newBuilder().addArgs("key").addArgs("member").build()));
413415

416+
transaction.zrank("key", "member");
417+
results.add(Pair.of(Zrank, ArgsArray.newBuilder().addArgs("key").addArgs("member").build()));
418+
419+
transaction.zrankWithScore("key", "member");
420+
results.add(
421+
Pair.of(
422+
Zrank,
423+
ArgsArray.newBuilder()
424+
.addArgs("key")
425+
.addArgs("member")
426+
.addArgs(WITH_SCORE_REDIS_API)
427+
.build()));
428+
414429
transaction.time();
415430
results.add(Pair.of(Time, ArgsArray.newBuilder().build()));
416431

java/integTest/src/test/java/glide/SharedCommandTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,30 @@ public void zscore(BaseClient client) {
11791179
assertTrue(executionException.getCause() instanceof RequestException);
11801180
}
11811181

1182+
@SneakyThrows
1183+
@ParameterizedTest
1184+
@MethodSource("getClients")
1185+
public void zrank(BaseClient client) {
1186+
String key = UUID.randomUUID().toString();
1187+
Map<String, Double> membersScores = Map.of("one", 1.5, "two", 2.0, "three", 3.0);
1188+
assertEquals(3, client.zadd(key, membersScores).get());
1189+
assertEquals(0, client.zrank(key, "one").get());
1190+
1191+
if (REDIS_VERSION.isGreaterThanOrEqualTo("7.2.0")) {
1192+
assertArrayEquals(new Object[] {0L, 1.5}, client.zrankWithScore(key, "one").get());
1193+
assertNull(client.zrankWithScore(key, "nonExistingMember").get());
1194+
assertNull(client.zrankWithScore("nonExistingKey", "nonExistingMember").get());
1195+
}
1196+
assertNull(client.zrank(key, "nonExistingMember").get());
1197+
assertNull(client.zrank("nonExistingKey", "nonExistingMember").get());
1198+
1199+
// Key exists, but it is not a set
1200+
assertEquals(OK, client.set(key, "value").get());
1201+
ExecutionException executionException =
1202+
assertThrows(ExecutionException.class, () -> client.zrank(key, "one").get());
1203+
assertTrue(executionException.getCause() instanceof RequestException);
1204+
}
1205+
11821206
@SneakyThrows
11831207
@ParameterizedTest
11841208
@MethodSource("getClients")

java/integTest/src/test/java/glide/TransactionTestUtilities.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public static BaseTransaction<?> transactionTest(BaseTransaction<?> baseTransact
8686
baseTransaction.smembers(key7);
8787

8888
baseTransaction.zadd(key8, Map.of("one", 1.0, "two", 2.0, "three", 3.0));
89+
baseTransaction.zrank(key8, "one");
8990
baseTransaction.zaddIncr(key8, "one", 3);
9091
baseTransaction.zrem(key8, new String[] {"one"});
9192
baseTransaction.zcard(key8);
@@ -150,6 +151,7 @@ public static Object[] transactionTestResult() {
150151
1L,
151152
Set.of("baz"),
152153
3L,
154+
0L, // zrank(key8, "one")
153155
4.0,
154156
1L,
155157
2L,

0 commit comments

Comments
 (0)