Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Rpush, Rpop and RpopCount Commands. (List Commands) #107

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.RPop;
import static redis_request.RedisRequestOuterClass.RequestType.RPush;
import static redis_request.RedisRequestOuterClass.RequestType.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
Expand All @@ -27,6 +29,7 @@
import glide.api.commands.ConnectionManagementCommands;
import glide.api.commands.GenericBaseCommands;
import glide.api.commands.HashCommands;
import glide.api.commands.ListBaseCommands;
import glide.api.commands.SetCommands;
import glide.api.commands.StringCommands;
import glide.api.models.commands.SetOptions;
Expand Down Expand Up @@ -60,6 +63,7 @@ public abstract class BaseClient
ConnectionManagementCommands,
StringCommands,
HashCommands,
ListBaseCommands,
SetCommands {
/** Redis simple string response with "OK" */
public static final String OK = ConstantResponse.OK.toString();
Expand Down Expand Up @@ -297,6 +301,26 @@ public CompletableFuture<Long> hdel(@NonNull String key, @NonNull String[] field
return commandManager.submitNewCommand(HashDel, args, this::handleLongResponse);
}

@Override
public CompletableFuture<Long> rpush(@NonNull String key, @NonNull String[] elements) {
String[] arguments = ArrayUtils.addFirst(elements, key);
return commandManager.submitNewCommand(RPush, arguments, this::handleLongResponse);
}

@Override
public CompletableFuture<String> rpop(@NonNull String key) {
return commandManager.submitNewCommand(
RPop, new String[] {key}, this::handleStringOrNullResponse);
}

@Override
public CompletableFuture<String[]> rpopCount(@NonNull String key, long count) {
return commandManager.submitNewCommand(
RPop,
new String[] {key, Long.toString(count)},
response -> castArray(handleArrayOrNullResponse(response), String.class));
}

@Override
public CompletableFuture<Long> sadd(String key, String[] members) {
String[] arguments = ArrayUtils.addFirst(members, key);
Expand Down
67 changes: 67 additions & 0 deletions java/client/src/main/java/glide/api/commands/ListBaseCommands.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.commands;

import java.util.concurrent.CompletableFuture;

/**
* List Commands interface for both standalone and cluster clients.
*
* @see <a href="https://redis.io/commands/?group=list">List Commands</a>
*/
public interface ListBaseCommands {
/**
* Inserts all the specified values at the tail of the list stored at <code>key</code>. <code>
* elements</code> are inserted one after the other to the tail of the list, from the leftmost
* element to the rightmost element. If <code>key</code> does not exist, it is created as an empty
* list before performing the push operations.
*
* @see <a href="https://redis.io/commands/rpush/">redis.io</a> for details.
* @param key The key of the list.
* @param elements The elements to insert at the tail of the list stored at <code>key</code>.
* @return The length of the list after the push operations.<br>
* @example
* <pre>
* Long pushCount1 = client.rpush("my_list", new String[] {"value1", "value2"}).get()
* assert pushCount1 == 2L
* Long pushCount2 = client.rpush("nonexistent_list", new String[] {"new_value"}).get()
* assert pushCount2 == 1
* </pre>
*/
CompletableFuture<Long> rpush(String key, String[] elements);

/**
* Removes and returns the last elements of the list stored at <code>key</code>. The command pops
* a single element from the end of the list.
*
* @see <a href="https://redis.io/commands/rpop/">redis.io</a> for details.
* @param key The key of the list.
* @return The value of the last element.<br>
* If <code>key</code> does not exist null will be returned.<br>
* @example
* <pre>
* String value1 = client.rpop("my_list").get()
* assert value1.equals("value1")
* String value2 = client.rpop("non_exiting_key").get()
* assert value2.equals(null)
* </pre>
*/
CompletableFuture<String> rpop(String key);

/**
* Removes and returns up to <code>count</code> elements from the list stored at <code>key</code>,
* depending on the list's length.
*
* @see <a href="https://redis.io/commands/rpop/">redis.io</a> for details.
* @param count The count of the elements to pop from the list.
* @returns An array of popped elements will be returned depending on the list's length.<br>
* If <code>key</code> does not exist null will be returned.<br>
* @example
* <pre>
* String[] values1 = client.rpop("my_list", 2).get()
* assert values1 == new String[] {"value1", "value2"}
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* String[] values2 = client.rpop("non_exiting_key" , 7).get()
* assert values2 == null
* </pre>
*/
CompletableFuture<String[]> rpopCount(String key, long count);
}
54 changes: 54 additions & 0 deletions java/client/src/main/java/glide/api/models/BaseTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.RPop;
import static redis_request.RedisRequestOuterClass.RequestType.RPush;
import static redis_request.RedisRequestOuterClass.RequestType.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
Expand Down Expand Up @@ -362,6 +364,58 @@ public T hdel(@NonNull String key, @NonNull String[] fields) {
return getThis();
}

/**
* Inserts all the specified values at the tail of the list stored at <code>key</code>. <code>
* elements</code> are inserted one after the other to the tail of the list, from the leftmost
* element to the rightmost element. If <code>key</code> does not exist, it is created as an empty
* list before performing the push operations.
*
* @see <a href="https://redis.io/commands/rpush/">redis.io</a> for details.
* @param key The key of the list.
* @param elements The elements to insert at the tail of the list stored at <code>key</code>.
* @return Command Response - The length of the list after the push operations.<br>
* If <code>key</code> holds a value that is not a list, an error is raised.<br>
*/
public T rpush(@NonNull String key, @NonNull String[] elements) {
ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key));

protobufTransaction.addCommands(buildCommand(RPush, commandArgs));
return getThis();
}

/**
* Removes and returns the last elements of the list stored at <code>key</code>. The command pops
* a single element from the end of the list.
*
* @see <a href="https://redis.io/commands/rpop/">redis.io</a> for details.
* @param key The key of the list.
* @return Command Response - The value of the last element.<br>
* If <code>key</code> does not exist null will be returned.<br>
*/
public T rpop(@NonNull String key) {
ArgsArray commandArgs = buildArgs(key);

protobufTransaction.addCommands(buildCommand(RPop, commandArgs));
return getThis();
}

/**
* Removes and returns up to <code>count</code> elements from the list stored at <code>key</code>,
* depending on the list's length.
*
* @see <a href="https://redis.io/commands/rpop/">redis.io</a> for details.
* @param count The count of the elements to pop from the list.
* @returns Command Response - An array of popped elements will be returned depending on the
* list's length.<br>
* If <code>key</code> does not exist null will be returned.<br>
*/
public T rpopCount(@NonNull String key, long count) {
ArgsArray commandArgs = buildArgs(key, Long.toString(count));

protobufTransaction.addCommands(buildCommand(RPop, commandArgs));
return getThis();
}

/**
* Add specified members to the set stored at <code>key</code>. Specified members that are already
* a member of this set are ignored.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Map;
import java.util.stream.Stream;

/** Utility methods for data conversion. */
public class ArrayTransformUtils {

/**
Expand All @@ -25,8 +26,9 @@ public static String[] convertMapToArgArray(Map<String, String> args) {
*
* @param objectArr Array of objects to cast.
* @param clazz The class of the array elements to cast to.
* @return An array of type T, containing the elements from the input array.
* @param <T> The type to which the elements are cast.
* @return An array of type U, containing the elements from the input array.
* @param <T> The base type from which the elements are being cast.
* @param <U> The subtype of T to which the elements are cast.
*/
@SuppressWarnings("unchecked")
public static <T, U extends T> U[] castArray(T[] objectArr, Class<U> clazz) {
Expand Down
70 changes: 70 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.RPop;
import static redis_request.RedisRequestOuterClass.RequestType.RPush;
import static redis_request.RedisRequestOuterClass.RequestType.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
Expand Down Expand Up @@ -568,6 +570,74 @@ public void hdel_success() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void rpush_returns_success() {
// setup
String key = "testKey";
String[] elements = new String[] {"value1", "value2"};
String[] args = new String[] {key, "value1", "value2"};
Long value = 2L;

CompletableFuture<Long> testResponse = mock(CompletableFuture.class);
when(testResponse.get()).thenReturn(value);
when(commandManager.<Long>submitNewCommand(eq(RPush), eq(args), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Long> response = service.rpush(key, elements);
Long payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void rpop_returns_success() {
// setup
String key = "testKey";
String value = "value";
String[] args = new String[] {key};

CompletableFuture<String> testResponse = mock(CompletableFuture.class);
when(testResponse.get()).thenReturn(value);
when(commandManager.<String>submitNewCommand(eq(RPop), eq(args), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<String> response = service.rpop(key);
String payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void rpopCount_returns_success() {
// setup
String key = "testKey";
long count = 2L;
String[] args = new String[] {key, Long.toString(count)};
String[] value = new String[] {"value1", "value2"};

CompletableFuture<String[]> testResponse = mock(CompletableFuture.class);
when(testResponse.get()).thenReturn(value);
when(commandManager.<String[]>submitNewCommand(eq(RPop), eq(args), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<String[]> response = service.rpopCount(key, count);
String[] payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void sadd_returns_success() {
Expand Down
11 changes: 11 additions & 0 deletions java/client/src/test/java/glide/api/models/TransactionTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.RPop;
import static redis_request.RedisRequestOuterClass.RequestType.RPush;
import static redis_request.RedisRequestOuterClass.RequestType.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
Expand Down Expand Up @@ -118,6 +120,15 @@ public void transaction_builds_protobuf_request(BaseTransaction<?> transaction)
transaction.hdel("key", new String[] {"field"});
results.add(Pair.of(HashDel, ArgsArray.newBuilder().addArgs("key").addArgs("field").build()));

transaction.rpush("key", new String[] {"element"});
results.add(Pair.of(RPush, ArgsArray.newBuilder().addArgs("key").addArgs("element").build()));

transaction.rpop("key");
results.add(Pair.of(RPop, ArgsArray.newBuilder().addArgs("key").build()));

transaction.rpopCount("key", 2);
results.add(Pair.of(RPop, ArgsArray.newBuilder().addArgs("key").addArgs("2").build()));

transaction.sadd("key", new String[] {"value"});
results.add(Pair.of(SAdd, ArgsArray.newBuilder().addArgs("key").addArgs("value").build()));

Expand Down
30 changes: 30 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,36 @@ public void hdel_multiple_existing_fields_non_existing_field_non_existing_key(Ba
assertEquals(0, client.hdel("non_existing_key", new String[] {field3}).get());
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
public void rpush_rpop_existing_non_existing_key(BaseClient client) {
String key = UUID.randomUUID().toString();
String[] valueArray = new String[] {"value1", "value2", "value3", "value4"};

assertEquals(4, client.rpush(key, valueArray).get());
assertEquals("value4", client.rpop(key).get());

assertArrayEquals(new String[] {"value3", "value2"}, client.rpopCount(key, 2).get());
assertNull(client.rpop("non_existing_key").get());
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
public void rpush_rpop_type_error(BaseClient client) {
String key = UUID.randomUUID().toString();

assertEquals(OK, client.set(key, "foo").get());

Exception rpushException =
assertThrows(ExecutionException.class, () -> client.rpush(key, new String[] {"foo"}).get());
assertTrue(rpushException.getCause() instanceof RequestException);

Exception rpopException = assertThrows(ExecutionException.class, () -> client.rpop(key).get());
assertTrue(rpopException.getCause() instanceof RequestException);
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
Expand Down
16 changes: 12 additions & 4 deletions java/integTest/src/test/java/glide/TransactionTestUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class TransactionTestUtilities {
private static final String key3 = "{key}" + UUID.randomUUID();
private static final String key4 = "{key}" + UUID.randomUUID();
private static final String key5 = "{key}" + UUID.randomUUID();
private static final String key6 = "{key}" + UUID.randomUUID();
private static final String value1 = UUID.randomUUID().toString();
private static final String value2 = UUID.randomUUID().toString();
private static final String field1 = UUID.randomUUID().toString();
Expand Down Expand Up @@ -46,10 +47,14 @@ public static BaseTransaction<?> transactionTest(BaseTransaction<?> baseTransact
baseTransaction.hget(key4, field1);
baseTransaction.hdel(key4, new String[] {field1});

baseTransaction.sadd(key5, new String[] {"baz", "foo"});
baseTransaction.srem(key5, new String[] {"foo"});
baseTransaction.scard(key5);
baseTransaction.smembers(key5);
baseTransaction.rpush(key5, new String[] {value1, value2, value2});
baseTransaction.rpop(key5);
baseTransaction.rpopCount(key5, 2);

baseTransaction.sadd(key6, new String[] {"baz", "foo"});
baseTransaction.srem(key6, new String[] {"foo"});
baseTransaction.scard(key6);
baseTransaction.smembers(key6);

return baseTransaction;
}
Expand All @@ -73,6 +78,9 @@ public static Object[] transactionTestResult() {
2L,
value1,
1L,
3L,
value2,
new String[] {value2, value1},
2L,
1L,
1L,
Expand Down
Loading