Skip to content

Commit

Permalink
Java: Adding command SInterCard (#1516)
Browse files Browse the repository at this point in the history
* Java: Adding command `SInterCard` (#335)

* Java: Adding command SInterCard

---------

Co-authored-by: TJ Zhang <tj.zhang@improving.com>
Co-authored-by: Yury-Fridlyand <yury.fridlyand@improving.com>

* addressing comments

---------

Co-authored-by: TJ Zhang <tj.zhang@improving.com>
Co-authored-by: Yury-Fridlyand <yury.fridlyand@improving.com>
  • Loading branch information
3 people authored Jun 4, 2024
1 parent 58d960b commit 1a15787
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 20 deletions.
17 changes: 17 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SDiff;
import static redis_request.RedisRequestOuterClass.RequestType.SDiffStore;
import static redis_request.RedisRequestOuterClass.RequestType.SInter;
import static redis_request.RedisRequestOuterClass.RequestType.SInterCard;
import static redis_request.RedisRequestOuterClass.RequestType.SInterStore;
import static redis_request.RedisRequestOuterClass.RequestType.SIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMIsMember;
Expand Down Expand Up @@ -1660,4 +1661,20 @@ public CompletableFuture<Long[]> bitfieldReadOnly(
arguments,
response -> castArray(handleArrayResponse(response), Long.class));
}

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

@Override
public CompletableFuture<Long> sintercard(@NonNull String[] keys, long limit) {
String[] arguments =
concatenateArrays(
new String[] {Long.toString(keys.length)},
keys,
new String[] {SET_LIMIT_REDIS_API, Long.toString(limit)});
return commandManager.submitNewCommand(SInterCard, arguments, this::handleLongResponse);
}
}
49 changes: 49 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 @@ -11,6 +11,9 @@
* @see <a href="https://redis.io/commands/?group=set">Set Commands</a>
*/
public interface SetBaseCommands {
/** Redis API keyword used to limit calculation of intersection of sorted sets. */
String SET_LIMIT_REDIS_API = "LIMIT";

/**
* Adds specified members to the set stored at <code>key</code>. Specified members that are
* already a member of this set are ignored.
Expand Down Expand Up @@ -186,6 +189,52 @@ public interface SetBaseCommands {
*/
CompletableFuture<Set<String>> sinter(String[] keys);

/**
* Gets the cardinality of the intersection of all the given sets.
*
* @since Redis 7.0 and above.
* @apiNote When in cluster mode, all <code>keys</code> must map to the same hash slot.
* @see <a href="https://redis.io/commands/sintercard/">redis.io</a> for details.
* @param keys The keys of the sets.
* @return The cardinality of the intersection result. If one or more sets do not exist, <code>0
* </code> is returned.
* @example
* <pre>{@code
* Long response = client.sintercard(new String[] {"set1", "set2"}).get();
* assertEquals(2L, response);
*
* Long emptyResponse = client.sintercard(new String[] {"set1", "nonExistingSet"}).get();
* assertEquals(emptyResponse, 0L);
* }</pre>
*/
CompletableFuture<Long> sintercard(String[] keys);

/**
* Gets the cardinality of the intersection of all the given sets.
*
* @since Redis 7.0 and above.
* @apiNote When in cluster mode, all <code>keys</code> must map to the same hash slot.
* @see <a href="https://redis.io/commands/sintercard/">redis.io</a> for details.
* @param keys The keys of the sets.
* @param limit The limit for the intersection cardinality value.
* @return The cardinality of the intersection result. If one or more sets do not exist, <code>0
* </code> is returned. If the intersection cardinality reaches <code>limit</code> partway
* through the computation, returns <code>limit</code> as the cardinality.
* @example
* <pre>{@code
* Long response = client.sintercard(new String[] {"set1", "set2"}, 3).get();
* assertEquals(2L, response);
*
* Long emptyResponse = client.sintercard(new String[] {"set1", "nonExistingSet"}, 3).get();
* assertEquals(emptyResponse, 0L);
*
* // when intersection cardinality > limit, returns limit as cardinality
* Long response2 = client.sintercard(new String[] {"set3", "set4"}, 3).get();
* assertEquals(3L, response2);
* }</pre>
*/
CompletableFuture<Long> sintercard(String[] keys, long limit);

/**
* Stores the members of the intersection of all given sets specified by <code>keys</code> into a
* new set at <code>destination</code>.
Expand Down
40 changes: 40 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 @@ -4,6 +4,7 @@
import static glide.api.commands.HashBaseCommands.WITH_VALUES_REDIS_API;
import static glide.api.commands.ListBaseCommands.COUNT_FOR_LIST_REDIS_API;
import static glide.api.commands.ServerManagementCommands.VERSION_REDIS_API;
import static glide.api.commands.SetBaseCommands.SET_LIMIT_REDIS_API;
import static glide.api.commands.SortedSetBaseCommands.COUNT_REDIS_API;
import static glide.api.commands.SortedSetBaseCommands.LIMIT_REDIS_API;
import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_REDIS_API;
Expand Down Expand Up @@ -110,6 +111,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SDiff;
import static redis_request.RedisRequestOuterClass.RequestType.SDiffStore;
import static redis_request.RedisRequestOuterClass.RequestType.SInter;
import static redis_request.RedisRequestOuterClass.RequestType.SInterCard;
import static redis_request.RedisRequestOuterClass.RequestType.SInterStore;
import static redis_request.RedisRequestOuterClass.RequestType.SIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMIsMember;
Expand Down Expand Up @@ -1221,6 +1223,44 @@ public T sinterstore(@NonNull String destination, @NonNull String[] keys) {
return getThis();
}

/**
* Gets the cardinality of the intersection of all the given sets.
*
* @since Redis 7.0 and above.
* @see <a href="https://redis.io/commands/sintercard/">redis.io</a> for details.
* @param keys The keys of the sets.
* @return Command Response - The cardinality of the intersection result. If one or more sets do
* not exist, <code>0</code> is returned.
*/
public T sintercard(@NonNull String[] keys) {
ArgsArray commandArgs =
buildArgs(concatenateArrays(new String[] {Long.toString(keys.length)}, keys));
protobufTransaction.addCommands(buildCommand(SInterCard, commandArgs));
return getThis();
}

/**
* Gets the cardinality of the intersection of all the given sets.
*
* @since Redis 7.0 and above.
* @see <a href="https://redis.io/commands/sintercard/">redis.io</a> for details.
* @param keys The keys of the sets.
* @param limit The limit for the intersection cardinality value.
* @return Command Response - The cardinality of the intersection result. If one or more sets do
* not exist, <code>0</code> is returned. If the intersection cardinality reaches <code>limit
* </code> partway through the computation, returns <code>limit</code> as the cardinality.
*/
public T sintercard(@NonNull String[] keys, long limit) {
ArgsArray commandArgs =
buildArgs(
concatenateArrays(
new String[] {Long.toString(keys.length)},
keys,
new String[] {SET_LIMIT_REDIS_API, Long.toString(limit)}));
protobufTransaction.addCommands(buildCommand(SInterCard, commandArgs));
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>.
Expand Down
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
Expand Up @@ -5,6 +5,7 @@
import static glide.api.commands.HashBaseCommands.WITH_VALUES_REDIS_API;
import static glide.api.commands.ListBaseCommands.COUNT_FOR_LIST_REDIS_API;
import static glide.api.commands.ServerManagementCommands.VERSION_REDIS_API;
import static glide.api.commands.SetBaseCommands.SET_LIMIT_REDIS_API;
import static glide.api.commands.SortedSetBaseCommands.LIMIT_REDIS_API;
import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_REDIS_API;
import static glide.api.commands.SortedSetBaseCommands.WITH_SCORE_REDIS_API;
Expand Down Expand Up @@ -137,6 +138,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SDiff;
import static redis_request.RedisRequestOuterClass.RequestType.SDiffStore;
import static redis_request.RedisRequestOuterClass.RequestType.SInter;
import static redis_request.RedisRequestOuterClass.RequestType.SInterCard;
import static redis_request.RedisRequestOuterClass.RequestType.SInterStore;
import static redis_request.RedisRequestOuterClass.RequestType.SIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMIsMember;
Expand Down Expand Up @@ -5367,6 +5369,57 @@ public void blmove_returns_success() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void sintercard_returns_success() {
// setup
String key1 = "testKey";
String key2 = "testKey2";
String[] arguments = new String[] {"2", key1, key2};
Long value = 1L;

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

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

// exercise
CompletableFuture<Long> response = service.sintercard(new String[] {key1, key2});
Long payload = response.get();

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

@SneakyThrows
@Test
public void sintercard_with_limit_returns_success() {
// setup
String key1 = "testKey";
String key2 = "testKey2";
long limit = 1L;
String[] arguments = new String[] {"2", key1, key2, SET_LIMIT_REDIS_API, "1"};
Long value = 1L;

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

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

// exercise
CompletableFuture<Long> response = service.sintercard(new String[] {key1, key2}, limit);
Long payload = response.get();

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

@SneakyThrows
@Test
public void srandmember_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SDiff;
import static redis_request.RedisRequestOuterClass.RequestType.SDiffStore;
import static redis_request.RedisRequestOuterClass.RequestType.SInter;
import static redis_request.RedisRequestOuterClass.RequestType.SInterCard;
import static redis_request.RedisRequestOuterClass.RequestType.SInterStore;
import static redis_request.RedisRequestOuterClass.RequestType.SIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMIsMember;
Expand Down Expand Up @@ -921,6 +922,12 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)),
BitFieldOptions.OFFSET_MULTIPLIER_PREFIX.concat("3"),
"4")));

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

transaction.sintercard(new String[] {"key1", "key2"}, 1);
results.add(Pair.of(SInterCard, buildArgs("2", "key1", "key2", "LIMIT", "1")));

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

for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) {
Expand Down
38 changes: 38 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -4862,4 +4862,42 @@ public void bitfield(BaseClient client) {
.get());
assertTrue(executionException.getCause() instanceof RequestException);
}

@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
public void sintercard(BaseClient client) {
assumeTrue(REDIS_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in redis 7.0.0");
// setup
String key1 = "{key}-1" + UUID.randomUUID();
String key2 = "{key}-2" + UUID.randomUUID();
String nonSetKey = "{key}-4" + UUID.randomUUID();
String[] saddargs = {"one", "two", "three", "four"};
String[] saddargs2 = {"two", "three", "four", "five"};
long limit = 2;
long limit2 = 4;

// keys does not exist or is empty
String[] keys = {key1, key2};
assertEquals(0, client.sintercard(keys).get());
assertEquals(0, client.sintercard(keys, limit).get());

// one of the keys is empty, intersection is empty, cardinality equals to 0
assertEquals(4, client.sadd(key1, saddargs).get());
assertEquals(0, client.sintercard(keys).get());

// sets at both keys have value, get cardinality of the intersection
assertEquals(4, client.sadd(key2, saddargs2).get());
assertEquals(3, client.sintercard(keys).get());

// returns limit as cardinality when the limit is reached partway through the computation
assertEquals(limit, client.sintercard(keys, limit).get());

// non set keys are used
assertEquals(OK, client.set(nonSetKey, "NotASet").get());
String[] badArr = new String[] {key1, nonSetKey};
ExecutionException executionException =
assertThrows(ExecutionException.class, () -> client.sintercard(badArr).get());
assertInstanceOf(RequestException.class, executionException.getCause());
}
}
62 changes: 43 additions & 19 deletions java/integTest/src/test/java/glide/TransactionTestUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ private static Object[] setCommands(BaseTransaction<?> transaction) {
String setKey2 = "{setKey}-2-" + UUID.randomUUID();
String setKey3 = "{setKey}-3-" + UUID.randomUUID();
String setKey4 = "{setKey}-4-" + UUID.randomUUID();
String setKey5 = "{setKey}-5-" + UUID.randomUUID();
String setKey6 = "{setKey}-6-" + UUID.randomUUID();

transaction
.sadd(setKey1, new String[] {"baz", "foo"})
Expand All @@ -395,25 +397,47 @@ private static Object[] setCommands(BaseTransaction<?> transaction) {
.srandmember(setKey4, 2)
.srandmember(setKey4, -2);

return new Object[] {
2L, // sadd(setKey1, new String[] {"baz", "foo"});
1L, // srem(setKey1, new String[] {"foo"});
1L, // scard(setKey1);
true, // sismember(setKey1, "baz")
Set.of("baz"), // smembers(setKey1);
new Boolean[] {true, false}, // smismembmer(setKey1, new String[] {"baz", "foo"})
Set.of("baz"), // sinter(new String[] { setKey1, setKey1 })
2L, // sadd(setKey2, new String[] { "a", "b" })
3L, // sunionstore(setKey3, new String[] { setKey2, setKey1 })
2L, // sdiffstore(setKey3, new String[] { setKey2, setKey1 })
0L, // sinterstore(setKey3, new String[] { setKey2, setKey1 })
Set.of("a", "b"), // sdiff(new String[] {setKey2, setKey3})
true, // smove(setKey1, setKey2, "baz")
1L, // sadd(setKey4, {"foo})
"foo", // srandmember(setKey4)
new String[] {"foo"}, // srandmember(setKey4, 2)
new String[] {"foo", "foo"}, // srandmember(setKey4, -2)
};
if (REDIS_VERSION.isGreaterThanOrEqualTo("7.0.0")) {
transaction
.sadd(setKey5, new String[] {"one", "two", "three", "four"})
.sadd(setKey6, new String[] {"two", "three", "four", "five"})
.sintercard(new String[] {setKey5, setKey6})
.sintercard(new String[] {setKey5, setKey6}, 2);
}

var expectedResults =
new Object[] {
2L, // sadd(setKey1, new String[] {"baz", "foo"});
1L, // srem(setKey1, new String[] {"foo"});
1L, // scard(setKey1);
true, // sismember(setKey1, "baz")
Set.of("baz"), // smembers(setKey1);
new Boolean[] {true, false}, // smismembmer(setKey1, new String[] {"baz", "foo"})
Set.of("baz"), // sinter(new String[] { setKey1, setKey1 })
2L, // sadd(setKey2, new String[] { "a", "b" })
3L, // sunionstore(setKey3, new String[] { setKey2, setKey1 })
2L, // sdiffstore(setKey3, new String[] { setKey2, setKey1 })
0L, // sinterstore(setKey3, new String[] { setKey2, setKey1 })
Set.of("a", "b"), // sdiff(new String[] {setKey2, setKey3})
true, // smove(setKey1, setKey2, "baz")
1L, // sadd(setKey4, {"foo})
"foo", // srandmember(setKey4)
new String[] {"foo"}, // srandmember(setKey4, 2)
new String[] {"foo", "foo"}, // srandmember(setKey4, -2)};
};
if (REDIS_VERSION.isGreaterThanOrEqualTo("7.0.0")) {
expectedResults =
concatenateArrays(
expectedResults,
new Object[] {
4L, // sadd(setKey5, {"one", "two", "three", "four"})
4L, // sadd(setKey6, {"two", "three", "four", "five"})
3L, // sintercard({setKey5, setKey6})
2L, // sintercard({setKey5, setKey6}, 2)
});
}

return expectedResults;
}

private static Object[] sortedSetCommands(BaseTransaction<?> transaction) {
Expand Down
5 changes: 4 additions & 1 deletion java/integTest/src/test/java/glide/cluster/CommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,10 @@ public static Stream<Arguments> callCrossSlotCommandsWhichShouldFail() {
Arguments.of(
"blmove",
"6.2.0",
clusterClient.blmove("abc", "def", ListDirection.LEFT, ListDirection.LEFT, 1)));
clusterClient.blmove("abc", "def", ListDirection.LEFT, ListDirection.LEFT, 1)),
Arguments.of("sintercard", "7.0.0", clusterClient.sintercard(new String[] {"abc", "def"})),
Arguments.of(
"sintercard", "7.0.0", clusterClient.sintercard(new String[] {"abc", "def"}, 1)));
}

@SneakyThrows
Expand Down

0 comments on commit 1a15787

Please sign in to comment.