Skip to content

Commit

Permalink
Java: Add SUNIONSTORE command. (valkey-io#1277)
Browse files Browse the repository at this point in the history
* Java: Add `SUNIONSTORE` command. (#203)

Signed-off-by: Yury-Fridlyand <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Co-authored-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
Yury-Fridlyand and acarbonetto authored Apr 17, 2024
1 parent 257eafb commit 788b23d
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 0 deletions.
1 change: 1 addition & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ enum RequestType {
ZRemRangeByLex = 108;
ZLexCount = 109;
Append = 110;
SUnionStore = 111;
SDiffStore = 112;
SInter = 113;
SInterStore = 114;
Expand Down
3 changes: 3 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ pub enum RequestType {
ZRemRangeByLex = 108,
ZLexCount = 109,
Append = 110,
SUnionStore = 111,
SDiffStore = 112,
SInter = 113,
SInterStore = 114,
Expand Down Expand Up @@ -250,6 +251,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::SDiffStore => RequestType::SDiffStore,
ProtobufRequestType::SInter => RequestType::SInter,
ProtobufRequestType::SInterStore => RequestType::SInterStore,
ProtobufRequestType::SUnionStore => RequestType::SUnionStore,
ProtobufRequestType::ZRangeStore => RequestType::ZRangeStore,
ProtobufRequestType::GetRange => RequestType::GetRange,
ProtobufRequestType::SMove => RequestType::SMove,
Expand Down Expand Up @@ -374,6 +376,7 @@ impl RequestType {
RequestType::SDiffStore => Some(cmd("SDIFFSTORE")),
RequestType::SInter => Some(cmd("SINTER")),
RequestType::SInterStore => Some(cmd("SINTERSTORE")),
RequestType::SUnionStore => Some(cmd("SUNIONSTORE")),
RequestType::ZRangeStore => Some(cmd("ZRANGESTORE")),
RequestType::GetRange => Some(cmd("GETRANGE")),
RequestType::SMove => Some(cmd("SMOVE")),
Expand Down
7 changes: 7 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SMove;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SUnionStore;
import static redis_request.RedisRequestOuterClass.RequestType.SetRange;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
Expand Down Expand Up @@ -574,6 +575,12 @@ public CompletableFuture<Set<String>> sinter(@NonNull String[] keys) {
return commandManager.submitNewCommand(SInter, keys, this::handleSetResponse);
}

@Override
public CompletableFuture<Long> sunionstore(@NonNull String destination, @NonNull String[] keys) {
String[] arguments = ArrayUtils.addFirst(keys, destination);
return commandManager.submitNewCommand(SUnionStore, arguments, this::handleLongResponse);
}

@Override
public CompletableFuture<Long> exists(@NonNull String[] keys) {
return commandManager.submitNewCommand(Exists, keys, this::handleLongResponse);
Expand Down
18 changes: 18 additions & 0 deletions java/client/src/main/java/glide/api/commands/SetBaseCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,22 @@ public interface SetBaseCommands {
* }</pre>
*/
CompletableFuture<Long> sinterstore(String destination, String[] keys);

/**
* Stores the members of the union of all given sets specified by <code>keys</code> into a new set
* at <code>destination</code>.
*
* @apiNote When in cluster mode, <code>destination</code> and all <code>keys</code> must map to
* the same <code>hash slot</code>.
* @see <a href="https://redis.io/commands/sunionstore/">redis.io</a> for details.
* @param destination The key of the destination set.
* @param keys The keys from which to retrieve the set members.
* @return The number of elements in the resulting set.
* @example
* <pre>{@code
* Long length = client.sunionstore("mySet", new String[] { "set1", "set2" }).get();
* assert length == 5L;
* }</pre>
*/
CompletableFuture<Long> sunionstore(String destination, String[] keys);
}
16 changes: 16 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 @@ -71,6 +71,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SMove;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SUnionStore;
import static redis_request.RedisRequestOuterClass.RequestType.SetRange;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
Expand Down Expand Up @@ -992,6 +993,21 @@ public T sinterstore(@NonNull String destination, @NonNull String[] keys) {
return getThis();
}

/**
* Stores the members of the union of all given sets specified by <code>keys</code> into a new set
* at <code>destination</code>.
*
* @see <a href="https://redis.io/commands/sunionstore/">redis.io</a> for details.
* @param destination The key of the destination set.
* @param keys The keys from which to retrieve the set members.
* @return Command Response - The number of elements in the resulting set.
*/
public T sunionstore(@NonNull String destination, @NonNull String[] keys) {
ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(keys, destination));
protobufTransaction.addCommands(buildCommand(SUnionStore, commandArgs));
return getThis();
}

/**
* Reads the configuration parameters of a running Redis server.
*
Expand Down
26 changes: 26 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SMove;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SUnionStore;
import static redis_request.RedisRequestOuterClass.RequestType.Select;
import static redis_request.RedisRequestOuterClass.RequestType.SetRange;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
Expand Down Expand Up @@ -1834,6 +1835,31 @@ public void sinterstore_returns_success() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void sunionstore_returns_success() {
// setup
String destination = "key";
String[] keys = new String[] {"set1", "set2"};
String[] args = new String[] {"key", "set1", "set2"};
Long value = 2L;

CompletableFuture<Long> testResponse = new CompletableFuture<>();
testResponse.complete(value);

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

// exercise
CompletableFuture<Long> response = service.sunionstore(destination, keys);
Long payload = response.get();

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

@SneakyThrows
@Test
public void zadd_noOptions_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SMove;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SUnionStore;
import static redis_request.RedisRequestOuterClass.RequestType.SetRange;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
Expand Down Expand Up @@ -285,6 +286,12 @@ public void transaction_builds_protobuf_request(BaseTransaction<?> transaction)
transaction.sinterstore("key", new String[] {"set1", "set2"});
results.add(Pair.of(SInterStore, buildArgs("key", "set1", "set2")));

transaction.sunionstore("key", new String[] {"set1", "set2"});
results.add(
Pair.of(
SUnionStore,
ArgsArray.newBuilder().addArgs("key").addArgs("set1").addArgs("set2").build()));

transaction.exists(new String[] {"key1", "key2"});
results.add(Pair.of(Exists, buildArgs("key1", "key2")));

Expand Down
53 changes: 53 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,59 @@ public void sinter(BaseClient client) {
}
}

@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
public void sunionstore(BaseClient client) {
String key1 = "{key}-1-" + UUID.randomUUID();
String key2 = "{key}-2-" + UUID.randomUUID();
String key3 = "{key}-3-" + UUID.randomUUID();
String key4 = "{key}-4-" + UUID.randomUUID();
String key5 = "{key}-5-" + UUID.randomUUID();

assertEquals(3, client.sadd(key1, new String[] {"a", "b", "c"}).get());
assertEquals(3, client.sadd(key2, new String[] {"c", "d", "e"}).get());
assertEquals(3, client.sadd(key4, new String[] {"e", "f", "g"}).get());

// create new
assertEquals(5, client.sunionstore(key3, new String[] {key1, key2}).get());
assertEquals(Set.of("a", "b", "c", "d", "e"), client.smembers(key3).get());

// overwrite existing set
assertEquals(5, client.sunionstore(key2, new String[] {key3, key2}).get());
assertEquals(Set.of("a", "b", "c", "d", "e"), client.smembers(key2).get());

// overwrite source
assertEquals(6, client.sunionstore(key1, new String[] {key1, key4}).get());
assertEquals(Set.of("a", "b", "c", "e", "f", "g"), client.smembers(key1).get());

// source key exists, but it is not a set
assertEquals(OK, client.set(key5, "value").get());
ExecutionException executionException =
assertThrows(
ExecutionException.class, () -> client.sunionstore(key1, new String[] {key5}).get());
assertInstanceOf(RequestException.class, executionException.getCause());

// overwrite destination - not a set
assertEquals(7, client.sunionstore(key5, new String[] {key1, key2}).get());
assertEquals(Set.of("a", "b", "c", "d", "e", "f", "g"), client.smembers(key5).get());

// wrong arguments
executionException =
assertThrows(ExecutionException.class, () -> client.sunionstore(key5, new String[0]).get());
assertInstanceOf(RequestException.class, executionException.getCause());

// same-slot requirement
if (client instanceof RedisClusterClient) {
executionException =
assertThrows(
ExecutionException.class,
() -> client.sunionstore("abc", new String[] {"zxy", "lkn"}).get());
assertInstanceOf(RequestException.class, executionException.getCause());
assertTrue(executionException.getMessage().toLowerCase().contains("crossslot"));
}
}

@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public static BaseTransaction<?> transactionTest(BaseTransaction<?> baseTransact
baseTransaction.sinter(new String[] {key7, key7});

baseTransaction.sadd(setKey2, new String[] {"a", "b"});
baseTransaction.sunionstore(setKey3, new String[] {setKey2, key7});
baseTransaction.sdiffstore(setKey3, new String[] {setKey2, key7});
baseTransaction.sinterstore(setKey3, new String[] {setKey2, key7});
baseTransaction.smove(key7, setKey2, "baz");
Expand Down Expand Up @@ -218,6 +219,7 @@ public static Object[] transactionTestResult() {
Set.of("baz"), // smembers(key7)
Set.of("baz"), // sinter(new String[] { key7, key7 })
2L, // sadd(setKey2, new String[] { "a", "b" })
3L, // sunionstore(setKey3, new String[] { setKey2, key7 })
2L, // sdiffstore(setKey3, new String[] { setKey2, key7 })
0L, // sinterstore(setKey3, new String[] { setKey2, key7 })
true, // smove(key7, setKey2, "baz")
Expand Down

0 comments on commit 788b23d

Please sign in to comment.