Skip to content

Commit

Permalink
Java: Add SADD, SREM, SMEMBERS, and SCARD commands (Set Commands)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaron-congo authored Feb 15, 2024
2 parents 98b1ed7 + a54b67d commit 69d0754
Show file tree
Hide file tree
Showing 8 changed files with 378 additions and 2 deletions.
38 changes: 37 additions & 1 deletion java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
import static glide.ffi.resolvers.SocketListenerResolver.getSocket;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;

import glide.api.commands.ConnectionManagementCommands;
import glide.api.commands.SetCommands;
import glide.api.commands.StringCommands;
import glide.api.models.commands.SetOptions;
import glide.api.models.configuration.BaseClientConfiguration;
Expand All @@ -20,6 +25,7 @@
import glide.managers.BaseCommandResponseResolver;
import glide.managers.CommandManager;
import glide.managers.ConnectionManager;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
Expand All @@ -32,7 +38,7 @@
/** Base Client class for Redis */
@AllArgsConstructor
public abstract class BaseClient
implements AutoCloseable, ConnectionManagementCommands, StringCommands {
implements AutoCloseable, ConnectionManagementCommands, StringCommands, SetCommands {
/** Redis simple string response with "OK" */
public static final String OK = ConstantResponse.OK.toString();

Expand Down Expand Up @@ -149,10 +155,18 @@ protected String handleStringOrNullResponse(Response response) throws RedisExcep
return handleRedisResponse(String.class, true, response);
}

protected Long handleLongResponse(Response response) throws RedisException {
return handleRedisResponse(Long.class, false, response);
}

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

protected Set<String> handleSetResponse(Response response) {
return handleRedisResponse(Set.class, false, response);
}

@Override
public CompletableFuture<String> ping() {
return commandManager.submitNewCommand(Ping, new String[0], this::handleStringResponse);
Expand Down Expand Up @@ -181,4 +195,26 @@ public CompletableFuture<String> set(
String[] arguments = ArrayUtils.addAll(new String[] {key, value}, options.toArgs());
return commandManager.submitNewCommand(SetString, arguments, this::handleStringOrNullResponse);
}

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

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

@Override
public CompletableFuture<Set<String>> smembers(String key) {
return commandManager.submitNewCommand(SMembers, new String[] {key}, this::handleSetResponse);
}

@Override
public CompletableFuture<Long> scard(String key) {
return commandManager.submitNewCommand(SCard, new String[] {key}, this::handleLongResponse);
}
}
77 changes: 77 additions & 0 deletions java/client/src/main/java/glide/api/commands/SetCommands.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.commands;

import java.util.Set;
import java.util.concurrent.CompletableFuture;

/**
* Set Commands interface.
*
* @see <a href="https://redis.io/commands/?group=set">Set Commands</a>
*/
public interface SetCommands {
/**
* Add specified members to the set stored at <code>key</code>. Specified members that are already
* a member of this set are ignored.
*
* @see <a href="https://redis.io/commands/sadd/">redis.io</a> for details.
* @param key The <code>key</code> where members will be added to its set.
* @param members A list of members to add to the set stored at <code>key</code>.
* @return The number of members that were added to the set, excluding members already present.
* @remarks If <code>key</code> does not exist, a new set is created before adding <code>members
* </code>.
* @example
* <p><code>
* int result = client.sadd("my_set", new String[]{"member1", "member2"}).get();
* // result: 2
* </code>
*/
CompletableFuture<Long> sadd(String key, String[] members);

/**
* Remove specified members from the set stored at <code>key</code>. Specified members that are
* not a member of this set are ignored.
*
* @see <a href="https://redis.io/commands/srem/">redis.io</a> for details.
* @param key The <code>key</code> from which members will be removed.
* @param members A list of members to remove from the set stored at <code>key</code>.
* @return The number of members that were removed from the set, excluding non-existing members.
* @remarks If <code>key</code> does not exist, it is treated as an empty set and this command
* returns 0.
* @example
* <p><code>
* int result = client.srem("my_set", new String[]{"member1", "member2"}).get();
* // result: 2
* </code>
*/
CompletableFuture<Long> srem(String key, String[] members);

/**
* Retrieve all the members of the set value stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/smembers/">redis.io</a> for details.
* @param key The key from which to retrieve the set members.
* @return A <code>Set</code> of all members of the set.
* @remarks If <code>key</code> does not exist an empty set will be returned.
* @example
* <p><code>
* {@literal Set<String>} result = client.smembers("my_set").get();
* // result: {"member1", "member2", "member3"}
* </code>
*/
CompletableFuture<Set<String>> smembers(String key);

/**
* Retrieve the set cardinality (number of elements) of the set stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/scard/">redis.io</a> for details.
* @param key The key from which to retrieve the number of set members.
* @return The cardinality (number of elements) of the set, or 0 if the key does not exist.
* @example
* <p><code>
* int result = client.scard("my_set").get();
* // result: 3
* </code>
*/
CompletableFuture<Long> scard(String key);
}
72 changes: 72 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 @@ -5,6 +5,10 @@
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;

import glide.api.models.commands.InfoOptions;
Expand Down Expand Up @@ -165,6 +169,74 @@ public T set(String key, String value, SetOptions options) {
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.
*
* @see <a href="https://redis.io/commands/sadd/">redis.io</a> for details.
* @param key The <code>key</code> where members will be added to its set.
* @param members A list of members to add to the set stored at <code>key</code>.
* @return Command Response - The number of members that were added to the set, excluding members
* already present.
* @remarks If <code>key</code> does not exist, a new set is created before adding <code>members
* </code>.
*/
public T sadd(String key, String[] members) {
ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(members, key));

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

/**
* Remove specified members from the set stored at <code>key</code>. Specified members that are
* not a member of this set are ignored.
*
* @see <a href="https://redis.io/commands/srem/">redis.io</a> for details.
* @param key The <code>key</code> from which members will be removed.
* @param members A list of members to remove from the set stored at <code>key</code>.
* @return Command Response - The number of members that were removed from the set, excluding
* non-existing members.
* @remarks If <code>key</code> does not exist, it is treated as an empty set and this command
* returns 0.
*/
public T srem(String key, String[] members) {
ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(members, key));

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

/**
* Retrieve all the members of the set value stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/smembers/">redis.io</a> for details.
* @param key The key from which to retrieve the set members.
* @return Command Response - A <code>Set</code> of all members of the set.
* @remarks If <code>key</code> does not exist an empty set will be returned.
*/
public T smembers(String key) {
ArgsArray commandArgs = buildArgs(key);

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

/**
* Retrieve the set cardinality (number of elements) of the set stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/scard/">redis.io</a> for details.
* @param key The key from which to retrieve the number of set members.
* @return Command Response - The cardinality (number of elements) of the set, or 0 if the key
* does not exist.
*/
public T scard(String key) {
ArgsArray commandArgs = buildArgs(key);

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

/** Build protobuf {@link Command} object for given command and arguments. */
protected Command buildCommand(RequestType requestType) {
return buildCommand(requestType, buildArgs());
Expand Down
102 changes: 102 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;

import glide.api.models.commands.InfoOptions;
import glide.api.models.commands.SetOptions;
import glide.api.models.commands.SetOptions.Expiry;
import glide.managers.CommandManager;
import glide.managers.ConnectionManager;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import lombok.SneakyThrows;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -267,4 +273,100 @@ public void info_with_empty_InfoOptions_returns_success() {
assertEquals(testResponse, response);
assertEquals(testPayload, payload);
}

@SneakyThrows
@Test
public void sadd_returns_success() {
// setup
String key = "testKey";
String[] members = new String[] {"testMember1", "testMember2"};
String[] arguments = ArrayUtils.addFirst(members, key);
Long value = 2L;

CompletableFuture<Long> testResponse = mock(CompletableFuture.class);
when(testResponse.get()).thenReturn(value);

// match on protobuf request
when(commandManager.<Long>submitNewCommand(eq(SAdd), eq(arguments), any()))
.thenReturn(testResponse);

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

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

@SneakyThrows
@Test
public void srem_returns_success() {
// setup
String key = "testKey";
String[] members = new String[] {"testMember1", "testMember2"};
String[] arguments = ArrayUtils.addFirst(members, key);
Long value = 2L;

CompletableFuture<Long> testResponse = mock(CompletableFuture.class);
when(testResponse.get()).thenReturn(value);

// match on protobuf request
when(commandManager.<Long>submitNewCommand(eq(SRem), eq(arguments), any()))
.thenReturn(testResponse);

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

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

@SneakyThrows
@Test
public void smembers_returns_success() {
// setup
String key = "testKey";
Set<String> value = Set.of("testMember");

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

// match on protobuf request
when(commandManager.<Set<String>>submitNewCommand(eq(SMembers), eq(new String[] {key}), any()))
.thenReturn(testResponse);

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

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

@SneakyThrows
@Test
public void scard_returns_success() {
// setup
String key = "testKey";
Long value = 2L;

CompletableFuture<Long> testResponse = mock(CompletableFuture.class);
when(testResponse.get()).thenReturn(value);

// match on protobuf request
when(commandManager.<Long>submitNewCommand(eq(SCard), eq(new String[] {key}), any()))
.thenReturn(testResponse);

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

// verify
assertEquals(testResponse, response);
assertEquals(value, payload);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;

import glide.api.models.commands.InfoOptions;
Expand Down Expand Up @@ -57,6 +61,18 @@ public void transaction_builds_protobuf_request() {
Info,
ArgsArray.newBuilder().addArgs(InfoOptions.Section.EVERYTHING.toString()).build()));

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

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

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

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

var protobufTransaction = transaction.getProtobufTransaction().build();

for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) {
Expand Down
Loading

0 comments on commit 69d0754

Please sign in to comment.