diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 1b92b5c9b1..6398c023fb 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -1146,6 +1146,11 @@ public CompletableFuture> sdiff(@NonNull String[] keys) { return commandManager.submitNewCommand(SDiff, keys, this::handleSetResponse); } + @Override + public CompletableFuture> sdiff(@NonNull GlideString[] keys) { + return commandManager.submitNewCommand(SDiff, keys, this::handleSetBinaryResponse); + } + @Override public CompletableFuture smismember(@NonNull String key, @NonNull String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); @@ -1167,6 +1172,13 @@ public CompletableFuture sdiffstore(@NonNull String destination, @NonNull return commandManager.submitNewCommand(SDiffStore, arguments, this::handleLongResponse); } + @Override + public CompletableFuture sdiffstore( + @NonNull GlideString destination, @NonNull GlideString[] keys) { + GlideString[] arguments = ArrayUtils.addFirst(keys, destination); + return commandManager.submitNewCommand(SDiffStore, arguments, this::handleLongResponse); + } + @Override public CompletableFuture smove( @NonNull String source, @NonNull String destination, @NonNull String member) { diff --git a/java/client/src/main/java/glide/api/commands/SetBaseCommands.java b/java/client/src/main/java/glide/api/commands/SetBaseCommands.java index 364bab4ff6..33e3759a29 100644 --- a/java/client/src/main/java/glide/api/commands/SetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SetBaseCommands.java @@ -276,6 +276,22 @@ public interface SetBaseCommands { */ CompletableFuture> sdiff(String[] keys); + /** + * Computes the difference between the first set and all the successive sets in keys. + * + * @apiNote When in cluster mode, all keys must map to the same hash slot. + * @see redis.io for details. + * @param keys The keys of the sets to diff. + * @return A Set of elements representing the difference between the sets.
+ * If the a key does not exist, it is treated as an empty set. + * @example + *
{@code
+     * Set values = client.sdiff(new GlideString[] {gs("set1"), gs("set2")}).get();
+     * assert values.contains(gs("element")); // Indicates that "element" is present in "set1", but missing in "set2"
+     * }
+ */ + CompletableFuture> sdiff(GlideString[] keys); + /** * Stores the difference between the first set and all the successive sets in keys * into a new set at destination. @@ -294,6 +310,24 @@ public interface SetBaseCommands { */ CompletableFuture sdiffstore(String destination, String[] keys); + /** + * Stores the difference between the first set and all the successive sets in keys + * into a new set at destination. + * + * @apiNote When in cluster mode, destination and all keys must map to + * the same hash slot. + * @see redis.io for details. + * @param destination The key of the destination set. + * @param keys The keys of the sets to diff. + * @return The number of elements in the resulting set. + * @example + *
{@code
+     * Long length = client.sdiffstore(gs("mySet"), new GlideString[] { gs("set1"), gs("set2") }).get();
+     * assert length == 5L;
+     * }
+ */ + CompletableFuture sdiffstore(GlideString destination, GlideString[] keys); + /** * Gets the intersection of all the given sets. * diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 5569ff7232..037fb6882a 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -3017,6 +3017,29 @@ public void sdiff_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void sdiff_binary_returns_success() { + // setup + GlideString[] keys = new GlideString[] {gs("key1"), gs("key2")}; + Set value = Set.of(gs("1"), gs("2")); + + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.>submitNewCommand(eq(SDiff), eq(keys), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = service.sdiff(keys); + Set payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void smismember_returns_success() { @@ -3094,6 +3117,33 @@ public void sdiffstore_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void sdiffstore_binary_returns_success() { + // setup + GlideString destination = gs("dest"); + GlideString[] keys = new GlideString[] {gs("set1"), gs("set2")}; + GlideString[] arguments = {gs("dest"), gs("set1"), gs("set2")}; + + Long value = 2L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(SDiffStore), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.sdiffstore(destination, keys); + + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void smove_returns_success() { diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index a65965a967..801f004a9d 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1752,6 +1752,35 @@ public void sdiff(BaseClient client) { assertInstanceOf(RequestException.class, executionException.getCause()); } + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void sdiff_gs(BaseClient client) { + GlideString key1 = gs("{key}-1-" + UUID.randomUUID()); + GlideString key2 = gs("{key}-2-" + UUID.randomUUID()); + GlideString key3 = gs("{key}-3-" + UUID.randomUUID()); + + assertEquals(3, client.sadd(key1, new GlideString[] {gs("a"), gs("b"), gs("c")}).get()); + assertEquals(3, client.sadd(key2, new GlideString[] {gs("c"), gs("d"), gs("e")}).get()); + + assertEquals(Set.of(gs("a"), gs("b")), client.sdiff(new GlideString[] {key1, key2}).get()); + assertEquals(Set.of(gs("d"), gs("e")), client.sdiff(new GlideString[] {key2, key1}).get()); + + // second set is empty + assertEquals( + Set.of(gs("a"), gs("b"), gs("c")), client.sdiff(new GlideString[] {key1, key3}).get()); + + // first set is empty + assertEquals(Set.of(), client.sdiff(new GlideString[] {key3, key1}).get()); + + // source key exists, but it is not a set + assertEquals(OK, client.set(key3, gs("value")).get()); + ExecutionException executionException = + assertThrows( + ExecutionException.class, () -> client.sdiff(new GlideString[] {key1, key3}).get()); + assertInstanceOf(RequestException.class, executionException.getCause()); + } + @SneakyThrows @ParameterizedTest(autoCloseArguments = false) @MethodSource("getClients") @@ -1852,6 +1881,59 @@ public void sdiffstore(BaseClient client) { assertInstanceOf(RequestException.class, executionException.getCause()); } + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void sdiffstore_gs(BaseClient client) { + GlideString key1 = gs("{key}-1-" + UUID.randomUUID()); + GlideString key2 = gs("{key}-2-" + UUID.randomUUID()); + GlideString key3 = gs("{key}-3-" + UUID.randomUUID()); + GlideString key4 = gs("{key}-4-" + UUID.randomUUID()); + GlideString key5 = gs("{key}-5-" + UUID.randomUUID()); + + assertEquals(3, client.sadd(key1, new GlideString[] {gs("a"), gs("b"), gs("c")}).get()); + assertEquals(3, client.sadd(key2, new GlideString[] {gs("c"), gs("d"), gs("e")}).get()); + assertEquals(3, client.sadd(key4, new GlideString[] {gs("e"), gs("f"), gs("g")}).get()); + + // create new + assertEquals(2, client.sdiffstore(key3, new GlideString[] {key1, key2}).get()); + assertEquals(Set.of(gs("a"), gs("b")), client.smembers(key3).get()); + + // overwrite existing set + assertEquals(2, client.sdiffstore(key2, new GlideString[] {key3, key2}).get()); + assertEquals(Set.of(gs("a"), gs("b")), client.smembers(key2).get()); + + // overwrite source + assertEquals(3, client.sdiffstore(key1, new GlideString[] {key1, key4}).get()); + assertEquals(Set.of(gs("a"), gs("b"), gs("c")), client.smembers(key1).get()); + + // diff with empty set + assertEquals(3, client.sdiffstore(key1, new GlideString[] {key1, key5}).get()); + assertEquals(Set.of(gs("a"), gs("b"), gs("c")), client.smembers(key1).get()); + + // diff empty with non-empty set + assertEquals(0, client.sdiffstore(key3, new GlideString[] {key5, key1}).get()); + assertEquals(Set.of(), client.smembers(key3).get()); + + // source key exists, but it is not a set + assertEquals(OK, client.set(key5, gs("value")).get()); + ExecutionException executionException = + assertThrows( + ExecutionException.class, + () -> client.sdiffstore(key1, new GlideString[] {key5}).get()); + assertInstanceOf(RequestException.class, executionException.getCause()); + + // overwrite destination - not a set + assertEquals(1, client.sdiffstore(key5, new GlideString[] {key1, key2}).get()); + assertEquals(Set.of(gs("c")), client.smembers(key5).get()); + + // wrong arguments + executionException = + assertThrows( + ExecutionException.class, () -> client.sdiffstore(key5, new GlideString[0]).get()); + assertInstanceOf(RequestException.class, executionException.getCause()); + } + @SneakyThrows @ParameterizedTest(autoCloseArguments = false) @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/cluster/CommandTests.java b/java/integTest/src/test/java/glide/cluster/CommandTests.java index 79106ec451..a3cca14dc1 100644 --- a/java/integTest/src/test/java/glide/cluster/CommandTests.java +++ b/java/integTest/src/test/java/glide/cluster/CommandTests.java @@ -755,8 +755,16 @@ public static Stream callCrossSlotCommandsWhichShouldFail() { null, clusterClient.sinterstore(gs("abc"), new GlideString[] {gs("zxy"), gs("lkn")})), Arguments.of("sdiff", null, clusterClient.sdiff(new String[] {"abc", "zxy", "lkn"})), + Arguments.of( + "sdiff_gs", + null, + clusterClient.sdiff(new GlideString[] {gs("abc"), gs("zxy"), gs("lkn")})), Arguments.of( "sdiffstore", null, clusterClient.sdiffstore("abc", new String[] {"zxy", "lkn"})), + Arguments.of( + "sdiffstore_gs", + null, + clusterClient.sdiffstore(gs("abc"), new GlideString[] {gs("zxy"), gs("lkn")})), Arguments.of("sinter", null, clusterClient.sinter(new String[] {"abc", "zxy", "lkn"})), Arguments.of( "sinter_gs",