Skip to content

Commit

Permalink
Java: Added Hget, Hset and Hdel to BaseClient and BaseTransaction. (H…
Browse files Browse the repository at this point in the history
…ash Commands) (valkey-io#959)

* Java: Added Hget, Hset and Hdel to BaseClient and BaseTransaction. (Hash Commands) (#82)

* Added transaction tests. Minor refactor documentation.

* Minor updates based on PR comments and rebase.

* Fixed TestUtilities, Spotless, minor refactor for code.

* Removed error condition in Hdel documentation.

* Minor documentation fixes.
  • Loading branch information
SanHalacogluImproving authored Feb 20, 2024
1 parent a35ed7d commit 2b9f9f7
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 7 deletions.
29 changes: 28 additions & 1 deletion java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import static redis_request.RedisRequestOuterClass.RequestType.Decr;
import static redis_request.RedisRequestOuterClass.RequestType.DecrBy;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.HashDel;
import static redis_request.RedisRequestOuterClass.RequestType.HashGet;
import static redis_request.RedisRequestOuterClass.RequestType.HashSet;
import static redis_request.RedisRequestOuterClass.RequestType.Incr;
import static redis_request.RedisRequestOuterClass.RequestType.IncrBy;
import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat;
Expand All @@ -20,6 +23,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SetString;

import glide.api.commands.ConnectionManagementCommands;
import glide.api.commands.HashCommands;
import glide.api.commands.SetCommands;
import glide.api.commands.StringCommands;
import glide.api.models.commands.SetOptions;
Expand Down Expand Up @@ -48,7 +52,11 @@
/** Base Client class for Redis */
@AllArgsConstructor
public abstract class BaseClient
implements AutoCloseable, ConnectionManagementCommands, StringCommands, SetCommands {
implements AutoCloseable,
ConnectionManagementCommands,
StringCommands,
HashCommands,
SetCommands {
/** Redis simple string response with "OK" */
public static final String OK = ConstantResponse.OK.toString();

Expand Down Expand Up @@ -261,6 +269,25 @@ public CompletableFuture<Long> decrBy(@NonNull String key, long amount) {
DecrBy, new String[] {key, Long.toString(amount)}, this::handleLongResponse);
}

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

@Override
public CompletableFuture<Long> hset(
@NonNull String key, @NonNull Map<String, String> fieldValueMap) {
String[] args = ArrayUtils.addFirst(convertMapToArgArray(fieldValueMap), key);
return commandManager.submitNewCommand(HashSet, args, this::handleLongResponse);
}

@Override
public CompletableFuture<Long> hdel(@NonNull String key, @NonNull String[] fields) {
String[] args = ArrayUtils.addFirst(fields, key);
return commandManager.submitNewCommand(HashDel, args, this::handleLongResponse);
}

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

import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
* Hash Commands interface for both standalone and cluster clients.
*
* @see <a href="https://redis.io/commands/?group=hash">Hash Commands</a>
*/
public interface HashCommands {

/**
* Retrieves the value associated with <code>field</code> in the hash stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/hget/">redis.io</a> for details.
* @param key The key of the hash.
* @param field The field in the hash stored at <code>key</code> to retrieve from the database.
* @return The value associated with <code>field</code>, or <code>null</code> when <code>field
* </code> is not present in the hash or <code>key</code> does not exist.
*/
CompletableFuture<String> hget(String key, String field);

/**
* Sets the specified fields to their respective values in the hash stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/hset/">redis.io</a> for details.
* @param key The key of the hash.
* @param fieldValueMap A field-value map consisting of fields and their corresponding values to
* be set in the hash stored at the specified key.
* @return The number of fields that were added.
*/
CompletableFuture<Long> hset(String key, Map<String, String> fieldValueMap);

/**
* Removes the specified fields from the hash stored at <code>key</code>. Specified fields that do
* not exist within this hash are ignored.
*
* @see <a href="https://redis.io/commands/hdel/">redis.io</a> for details.
* @param key The key of the hash.
* @param fields The fields to remove from the hash stored at <code>key</code>.
* @return The number of fields that were removed from the hash, not including specified but
* non-existing fields.<br>
* If <code>key</code> does not exist, it is treated as an empty hash and it returns 0.<br>
*/
CompletableFuture<Long> hdel(String key, String[] fields);
}
55 changes: 55 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 @@ -6,6 +6,9 @@
import static redis_request.RedisRequestOuterClass.RequestType.Decr;
import static redis_request.RedisRequestOuterClass.RequestType.DecrBy;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.HashDel;
import static redis_request.RedisRequestOuterClass.RequestType.HashGet;
import static redis_request.RedisRequestOuterClass.RequestType.HashSet;
import static redis_request.RedisRequestOuterClass.RequestType.Incr;
import static redis_request.RedisRequestOuterClass.RequestType.IncrBy;
import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat;
Expand Down Expand Up @@ -290,6 +293,58 @@ public T decrBy(@NonNull String key, long amount) {
return getThis();
}

/**
* Retrieve the value associated with <code>field</code> in the hash stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/hget/">redis.io</a> for details.
* @param key The key of the hash.
* @param field The field in the hash stored at <code>key</code> to retrieve from the database.
* @return Command Response - The value associated with <code>field</code>, or <code>null</code>
* when <code>field
* </code> is not present in the hash or <code>key</code> does not exist.
*/
public T hget(@NonNull String key, @NonNull String field) {
ArgsArray commandArgs = buildArgs(key, field);

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

/**
* Sets the specified fields to their respective values in the hash stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/hset/">redis.io</a> for details.
* @param key The key of the hash.
* @param fieldValueMap A field-value map consisting of fields and their corresponding values to
* be set in the hash stored at the specified key.
* @return Command Response - The number of fields that were added.
*/
public T hset(@NonNull String key, @NonNull Map<String, String> fieldValueMap) {
ArgsArray commandArgs =
buildArgs(ArrayUtils.addFirst(convertMapToArgArray(fieldValueMap), key));

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

/**
* Removes the specified fields from the hash stored at <code>key</code>. Specified fields that do
* not exist within this hash are ignored.
*
* @see <a href="https://redis.io/commands/hdel/">redis.io</a> for details.
* @param key The key of the hash.
* @param fields The fields to remove from the hash stored at <code>key</code>.
* @return Command Response - The number of fields that were removed from the hash, not including
* specified but non-existing fields.<br>
* If <code>key</code> does not exist, it is treated as an empty hash and it returns 0.<br>
*/
public T hdel(@NonNull String key, @NonNull String[] fields) {
ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(fields, key));

protobufTransaction.addCommands(buildCommand(HashDel, 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
74 changes: 74 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import static redis_request.RedisRequestOuterClass.RequestType.Decr;
import static redis_request.RedisRequestOuterClass.RequestType.DecrBy;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.HashDel;
import static redis_request.RedisRequestOuterClass.RequestType.HashGet;
import static redis_request.RedisRequestOuterClass.RequestType.HashSet;
import static redis_request.RedisRequestOuterClass.RequestType.Incr;
import static redis_request.RedisRequestOuterClass.RequestType.IncrBy;
import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat;
Expand Down Expand Up @@ -453,6 +456,77 @@ public void decrBy_returns_success() {
assertEquals(value, payload);
}

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

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

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

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

@SneakyThrows
@Test
public void hset_success() {
// setup
String key = "testKey";
Map<String, String> fieldValueMap = new LinkedHashMap<>();
fieldValueMap.put("field1", "value1");
fieldValueMap.put("field2", "value2");
String[] args = new String[] {key, "field1", "value1", "field2", "value2"};
Long value = 2L;

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

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

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

@SneakyThrows
@Test
public void hdel_success() {
// setup
String key = "testKey";
String[] fields = {"testField1", "testField2"};
String[] args = {key, "testField1", "testField2"};
Long value = 2L;

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

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

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

@SneakyThrows
@Test
public void sadd_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import static redis_request.RedisRequestOuterClass.RequestType.Decr;
import static redis_request.RedisRequestOuterClass.RequestType.DecrBy;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.HashDel;
import static redis_request.RedisRequestOuterClass.RequestType.HashGet;
import static redis_request.RedisRequestOuterClass.RequestType.HashSet;
import static redis_request.RedisRequestOuterClass.RequestType.Incr;
import static redis_request.RedisRequestOuterClass.RequestType.IncrBy;
import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat;
Expand Down Expand Up @@ -90,6 +93,18 @@ public void transaction_builds_protobuf_request() {
transaction.decrBy("key", 2);
results.add(Pair.of(DecrBy, ArgsArray.newBuilder().addArgs("key").addArgs("2").build()));

transaction.hset("key", Map.of("field", "value"));
results.add(
Pair.of(
HashSet,
ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("value").build()));

transaction.hget("key", "field");
results.add(Pair.of(HashGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build()));

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

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

Expand Down
15 changes: 15 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 @@ -6,6 +6,9 @@
import static redis_request.RedisRequestOuterClass.RequestType.Decr;
import static redis_request.RedisRequestOuterClass.RequestType.DecrBy;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.HashDel;
import static redis_request.RedisRequestOuterClass.RequestType.HashGet;
import static redis_request.RedisRequestOuterClass.RequestType.HashSet;
import static redis_request.RedisRequestOuterClass.RequestType.Incr;
import static redis_request.RedisRequestOuterClass.RequestType.IncrBy;
import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat;
Expand Down Expand Up @@ -89,6 +92,18 @@ public void transaction_builds_protobuf_request() {
transaction.decrBy("key", 2);
results.add(Pair.of(DecrBy, ArgsArray.newBuilder().addArgs("key").addArgs("2").build()));

transaction.hset("key", Map.of("field", "value"));
results.add(
Pair.of(
HashSet,
ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("value").build()));

transaction.hget("key", "field");
results.add(Pair.of(HashGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build()));

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

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

Expand Down
33 changes: 33 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,39 @@ public void decr_and_decrBy_non_existing_key(BaseClient client) {
assertEquals("-3", client.get(key2).get());
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
public void hset_hget_existing_fields_non_existing_fields(BaseClient client) {
String key = UUID.randomUUID().toString();
String field1 = UUID.randomUUID().toString();
String field2 = UUID.randomUUID().toString();
String value = UUID.randomUUID().toString();
Map<String, String> fieldValueMap = Map.of(field1, value, field2, value);

assertEquals(2, client.hset(key, fieldValueMap).get());
assertEquals(value, client.hget(key, field1).get());
assertEquals(value, client.hget(key, field2).get());
assertNull(client.hget(key, "non_existing_field").get());
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
public void hdel_multiple_existing_fields_non_existing_field_non_existing_key(BaseClient client) {
String key = UUID.randomUUID().toString();
String field1 = UUID.randomUUID().toString();
String field2 = UUID.randomUUID().toString();
String field3 = UUID.randomUUID().toString();
String value = UUID.randomUUID().toString();
Map<String, String> fieldValueMap = Map.of(field1, value, field2, value, field3, value);

assertEquals(3, client.hset(key, fieldValueMap).get());
assertEquals(2, client.hdel(key, new String[] {field1, field2}).get());
assertEquals(0, client.hdel(key, new String[] {"non_existing_field"}).get());
assertEquals(0, client.hdel("non_existing_key", new String[] {field3}).get());
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
Expand Down
Loading

0 comments on commit 2b9f9f7

Please sign in to comment.