Skip to content

Commit

Permalink
Java: Added Mget and Mset commands to BaseClient and BaseTransactions…
Browse files Browse the repository at this point in the history
…. (String Commands) (valkey-io#955)

* Added Mget and Mset commands. (#78)

* Added Mget and Mset commands to BaseClient and BaseTransactions.

* Added 2 util functions for code cleanup. Minor documentation refactor.

* Added transaction tests.

* Added documentation for CommandUtils.

* Minor changes based on PR comments

* Minor changes based on PR comments

* Fix merge conflicts; spotless

Signed-off-by: Andrew Carbonetto <[email protected]>

* Minor changes from PR comments.

* Reorder commands

Signed-off-by: Andrew Carbonetto <[email protected]>

* Spotless.

---------

Signed-off-by: Andrew Carbonetto <[email protected]>
Co-authored-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
SanHalacogluImproving and acarbonetto authored Feb 20, 2024
1 parent 66793f2 commit a1facbf
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 8 deletions.
22 changes: 21 additions & 1 deletion java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
package glide.api;

import static glide.ffi.resolvers.SocketListenerResolver.getSocket;
import static glide.utils.ArrayTransformUtils.castArray;
import static glide.utils.ArrayTransformUtils.convertMapToArgArray;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
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.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
Expand Down Expand Up @@ -157,13 +161,17 @@ protected Long handleLongResponse(Response response) throws RedisException {
}

protected Object[] handleArrayResponse(Response response) throws RedisException {
return handleRedisResponse(Object[].class, false, response);
}

protected Object[] handleArrayOrNullResponse(Response response) throws RedisException {
return handleRedisResponse(Object[].class, true, response);
}

/**
* @param response A Protobuf response
* @return A map of <code>String</code> to <code>V</code>
* @param <V> Value type, could be even map too
* @param <V> Value type could be even map too
*/
@SuppressWarnings("unchecked") // raw Map cast to Map<String, V>
protected <V> Map<String, V> handleMapResponse(Response response) throws RedisException {
Expand Down Expand Up @@ -204,6 +212,18 @@ public CompletableFuture<String> set(
return commandManager.submitNewCommand(SetString, arguments, this::handleStringOrNullResponse);
}

@Override
public CompletableFuture<String[]> mget(@NonNull String[] keys) {
return commandManager.submitNewCommand(
MGet, keys, response -> castArray(handleArrayOrNullResponse(response), String.class));
}

@Override
public CompletableFuture<String> mset(@NonNull Map<String, String> keyValueMap) {
String[] args = convertMapToArgArray(keyValueMap);
return commandManager.submitNewCommand(MSet, args, this::handleStringResponse);
}

@Override
public CompletableFuture<Long> sadd(String key, String[] members) {
String[] arguments = ArrayUtils.addFirst(members, key);
Expand Down
2 changes: 1 addition & 1 deletion java/client/src/main/java/glide/api/RedisClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public CompletableFuture<Object> customCommand(@NonNull String[] args) {

@Override
public CompletableFuture<Object[]> exec(Transaction transaction) {
return commandManager.submitNewCommand(transaction, this::handleArrayResponse);
return commandManager.submitNewCommand(transaction, this::handleArrayOrNullResponse);
}

@Override
Expand Down
21 changes: 21 additions & 0 deletions java/client/src/main/java/glide/api/commands/StringCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import glide.api.models.commands.SetOptions;
import glide.api.models.commands.SetOptions.ConditionalSet;
import glide.api.models.commands.SetOptions.SetOptionsBuilder;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
Expand Down Expand Up @@ -48,4 +49,24 @@ public interface StringCommands {
* is set, return the old value as a <code>String</code>.
*/
CompletableFuture<String> set(String key, String value, SetOptions options);

/**
* Retrieve the values of multiple <code>keys</code>.
*
* @see <a href="https://redis.io/commands/mget/">redis.io</a> for details.
* @param keys A list of keys to retrieve values for.
* @return An array of values corresponding to the provided <code>keys</code>.<br>
* If a <code>key</code>is not found, its corresponding value in the list will be <code>null
* </code>.
*/
CompletableFuture<String[]> mget(String[] keys);

/**
* Set multiple keys to multiple values in a single operation.
*
* @see <a href="https://redis.io/commands/mset/">redis.io</a> for details.
* @param keyValueMap A key-value map consisting of keys and their respective values to set.
* @return Always <code>OK</code>.
*/
CompletableFuture<String> mset(Map<String, String> keyValueMap);
}
37 changes: 37 additions & 0 deletions java/client/src/main/java/glide/api/models/BaseTransaction.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.models;

import static glide.utils.ArrayTransformUtils.convertMapToArgArray;
import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
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.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
Expand All @@ -16,7 +19,9 @@
import glide.api.models.commands.SetOptions;
import glide.api.models.commands.SetOptions.ConditionalSet;
import glide.api.models.commands.SetOptions.SetOptionsBuilder;
import java.util.Map;
import lombok.Getter;
import lombok.NonNull;
import org.apache.commons.lang3.ArrayUtils;
import redis_request.RedisRequestOuterClass.Command;
import redis_request.RedisRequestOuterClass.Command.ArgsArray;
Expand Down Expand Up @@ -168,6 +173,38 @@ public T set(String key, String value, SetOptions options) {
return getThis();
}

/**
* Retrieve the values of multiple <code>keys</code>.
*
* @see <a href="https://redis.io/commands/mget/">redis.io</a> for details.
* @param keys A list of keys to retrieve values for.
* @return Command Response - An array of values corresponding to the provided <code>keys</code>.
* <br>
* If a <code>key</code>is not found, its corresponding value in the list will be <code>null
* </code>.
*/
public T mget(@NonNull String[] keys) {
ArgsArray commandArgs = buildArgs(keys);

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

/**
* Set multiple keys to multiple values in a single operation.
*
* @see <a href="https://redis.io/commands/mset/">redis.io</a> for details.
* @param keyValueMap A key-value map consisting of keys and their respective values to set.
* @return Command Response - Always <code>OK</code>.
*/
public T mset(@NonNull Map<String, String> keyValueMap) {
String[] args = convertMapToArgArray(keyValueMap);
ArgsArray commandArgs = buildArgs(args);

protobufTransaction.addCommands(buildCommand(MSet, 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
37 changes: 37 additions & 0 deletions java/client/src/main/java/glide/utils/ArrayTransformUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.utils;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Stream;

public class ArrayTransformUtils {

/**
* Converts a map to an array of strings with alternating keys and values.
*
* @param args Map of string pairs to convert.
* @return Array of strings [key1, value1, key2, value2, ...].
*/
public static String[] convertMapToArgArray(Map<String, String> args) {
return args.entrySet().stream()
.flatMap(entry -> Stream.of(entry.getKey(), entry.getValue()))
.toArray(String[]::new);
}

/**
* Casts an array of objects to an array of type T.
*
* @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.
*/
@SuppressWarnings("unchecked")
public static <T, U extends T> U[] castArray(T[] objectArr, Class<U> clazz) {
return Arrays.stream(objectArr)
.map(clazz::cast)
.toArray(size -> (U[]) Array.newInstance(clazz, size));
}
}
53 changes: 53 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api;

import static glide.api.BaseClient.OK;
import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST;
import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_EXISTS;
import static glide.api.models.commands.SetOptions.RETURN_OLD_VALUE;
Expand All @@ -14,6 +15,8 @@
import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
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.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
Expand All @@ -26,6 +29,8 @@
import glide.api.models.commands.SetOptions.Expiry;
import glide.managers.CommandManager;
import glide.managers.ConnectionManager;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import lombok.SneakyThrows;
Expand Down Expand Up @@ -274,6 +279,54 @@ public void info_with_empty_InfoOptions_returns_success() {
assertEquals(testPayload, payload);
}

@SneakyThrows
@Test
public void mget_returns_success() {
// setup
String[] keys = {"key1", null, "key2"};
String[] values = {"value1", null, "value2"};

CompletableFuture testResponse = mock(CompletableFuture.class);
when(testResponse.get()).thenReturn(values);

// match on protobuf request
when(commandManager.<String>submitNewCommand(eq(MGet), eq(keys), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<String[]> response = service.mget(keys);
String[] payload = response.get();

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

@SneakyThrows
@Test
public void mset_returns_success() {
// setup
Map<String, String> keyValueMap = new LinkedHashMap<>();
keyValueMap.put("key1", "value1");
keyValueMap.put("key2", "value2");
String[] args = {"key1", "value1", "key2", "value2"};

CompletableFuture<String> testResponse = mock(CompletableFuture.class);
when(testResponse.get()).thenReturn(OK);

// match on protobuf request
when(commandManager.<String>submitNewCommand(eq(MSet), eq(args), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<String> response = service.mset(keyValueMap);
String payload = response.get();

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

@SneakyThrows
@Test
public void sadd_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
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.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
Expand All @@ -16,6 +18,7 @@
import glide.api.models.commands.SetOptions;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.jupiter.api.Test;
import redis_request.RedisRequestOuterClass.Command;
Expand Down Expand Up @@ -60,6 +63,12 @@ public void transaction_builds_protobuf_request() {
Info,
ArgsArray.newBuilder().addArgs(InfoOptions.Section.EVERYTHING.toString()).build()));

transaction.mset(Map.of("key", "value"));
results.add(Pair.of(MSet, ArgsArray.newBuilder().addArgs("key").addArgs("value").build()));

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

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

Expand Down
19 changes: 19 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST;
import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_EXISTS;
import static glide.api.models.commands.SetOptions.Expiry.Milliseconds;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand All @@ -21,6 +22,7 @@
import glide.api.models.configuration.RedisClusterClientConfiguration;
import glide.api.models.exceptions.RequestException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -253,6 +255,23 @@ public void set_missing_value_and_returnOldValue_is_null(BaseClient client) {
assertNull(data);
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
public void mset_mget_existing_non_existing_key(BaseClient client) {
String key1 = UUID.randomUUID().toString();
String key2 = UUID.randomUUID().toString();
String key3 = UUID.randomUUID().toString();
String nonExisting = UUID.randomUUID().toString();
String value = UUID.randomUUID().toString();
Map<String, String> keyValueMap = Map.of(key1, value, key2, value, key3, value);

assertEquals(OK, client.mset(keyValueMap).get());
assertArrayEquals(
new String[] {value, value, null, value},
client.mget(new String[] {key1, key2, nonExisting, key3}).get());
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
Expand Down
31 changes: 25 additions & 6 deletions java/integTest/src/test/java/glide/TestUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@

import glide.api.models.BaseTransaction;
import glide.api.models.commands.SetOptions;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

public class TestUtilities {
private static final String key1 = "{key}" + UUID.randomUUID();
private static final String key2 = "{key}" + UUID.randomUUID();
private static final String key3 = "{key}" + UUID.randomUUID();
private static final String value1 = "{value}" + UUID.randomUUID();
private static final String value2 = "{value}" + UUID.randomUUID();

public static BaseTransaction transactionTest(BaseTransaction baseTransaction) {
String key1 = "{key}" + UUID.randomUUID();
String key2 = "{key}" + UUID.randomUUID();
String key3 = "{key}" + UUID.randomUUID();

baseTransaction.set(key1, "bar");
baseTransaction.set(key2, "baz", SetOptions.builder().returnOldValue(true).build());
baseTransaction.set(key1, value1);
baseTransaction.get(key1);

baseTransaction.set(key2, value2, SetOptions.builder().returnOldValue(true).build());
baseTransaction.customCommand("MGET", key1, key2);

baseTransaction.mset(Map.of(key1, value2, key2, value1));
baseTransaction.mget(new String[] {key1, key2});

baseTransaction.sadd(key3, new String[] {"baz", "foo"});
baseTransaction.srem(key3, new String[] {"foo"});
baseTransaction.scard(key3);
Expand All @@ -26,6 +34,17 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransaction) {
}

public static Object[] transactionTestResult() {
return new Object[] {"OK", null, new String[] {"bar", "baz"}, 2L, 1L, 1L, Set.of("baz")};
return new Object[] {
"OK",
value1,
null,
new String[] {value1, value2},
"OK",
new String[] {value2, value1},
2L,
1L,
1L,
Set.of("baz"),
};
}
}

0 comments on commit a1facbf

Please sign in to comment.