Skip to content

Commit

Permalink
Add SMISMEMBER command. (valkey-io#1296)
Browse files Browse the repository at this point in the history
* Add `SMISMEMBER` command. (#191)

Signed-off-by: Yury-Fridlyand <[email protected]>
  • Loading branch information
Yury-Fridlyand authored Apr 17, 2024
1 parent 788b23d commit 3b29627
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 0 deletions.
32 changes: 32 additions & 0 deletions glide-core/src/client/value_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(crate) enum ExpectedReturnType {
DoubleOrNull,
ZrankReturnType,
JsonToggleReturnType,
ArrayOfBools,
}

pub(crate) fn convert_to_expected_type(
Expand Down Expand Up @@ -159,6 +160,21 @@ pub(crate) fn convert_to_expected_type(
)
.into()),
},
ExpectedReturnType::ArrayOfBools => match value {
Value::Array(array) => {
let array_of_bools = array
.iter()
.map(|v| Value::Boolean(from_owned_redis_value::<bool>(v.clone()).unwrap()))
.collect();
Ok(Value::Array(array_of_bools))
}
_ => Err((
ErrorKind::TypeError,
"Response couldn't be converted to an array of boolean",
format!("(response was {:?})", value),
)
.into()),
},
}
}

Expand Down Expand Up @@ -221,6 +237,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
b"INCRBYFLOAT" | b"HINCRBYFLOAT" => Some(ExpectedReturnType::Double),
b"HEXISTS" | b"HSETNX" | b"EXPIRE" | b"EXPIREAT" | b"PEXPIRE" | b"PEXPIREAT"
| b"SISMEMBER" | b"PERSIST" | b"SMOVE" => Some(ExpectedReturnType::Boolean),
b"SMISMEMBER" => Some(ExpectedReturnType::ArrayOfBools),
b"SMEMBERS" | b"SINTER" => Some(ExpectedReturnType::Set),
b"ZSCORE" => Some(ExpectedReturnType::DoubleOrNull),
b"ZPOPMIN" | b"ZPOPMAX" => Some(ExpectedReturnType::MapOfStringToDouble),
Expand Down Expand Up @@ -249,6 +266,21 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
mod tests {
use super::*;

#[test]
fn convert_smismember() {
assert!(matches!(
expected_type_for_cmd(redis::cmd("SMISMEMBER").arg("key").arg("elem")),
Some(ExpectedReturnType::ArrayOfBools)
));

let redis_response = Value::Array(vec![Value::Int(0), Value::Int(1)]);
let converted_response =
convert_to_expected_type(redis_response, Some(ExpectedReturnType::ArrayOfBools))
.unwrap();
let expected_response = Value::Array(vec![Value::Boolean(false), Value::Boolean(true)]);
assert_eq!(expected_response, converted_response);
}

#[test]
fn convert_zadd_only_if_incr_is_included() {
assert!(matches!(
Expand Down
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 @@ -158,6 +158,7 @@ enum RequestType {
ZRangeStore = 115;
GetRange = 116;
SMove = 117;
SMIsMember = 118;
}

message Command {
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 @@ -126,6 +126,7 @@ pub enum RequestType {
ZRangeStore = 115,
GetRange = 116,
SMove = 117,
SMIsMember = 118,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -255,6 +256,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::ZRangeStore => RequestType::ZRangeStore,
ProtobufRequestType::GetRange => RequestType::GetRange,
ProtobufRequestType::SMove => RequestType::SMove,
ProtobufRequestType::SMIsMember => RequestType::SMIsMember,
}
}
}
Expand Down Expand Up @@ -380,6 +382,7 @@ impl RequestType {
RequestType::ZRangeStore => Some(cmd("ZRANGESTORE")),
RequestType::GetRange => Some(cmd("GETRANGE")),
RequestType::SMove => Some(cmd("SMOVE")),
RequestType::SMIsMember => Some(cmd("SMISMEMBER")),
}
}
}
8 changes: 8 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SInter;
import static redis_request.RedisRequestOuterClass.RequestType.SInterStore;
import static redis_request.RedisRequestOuterClass.RequestType.SIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SMove;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
Expand Down Expand Up @@ -551,6 +552,13 @@ public CompletableFuture<Long> scard(@NonNull String key) {
return commandManager.submitNewCommand(SCard, new String[] {key}, this::handleLongResponse);
}

@Override
public CompletableFuture<Boolean[]> smismember(@NonNull String key, @NonNull String[] members) {
String[] arguments = ArrayUtils.addFirst(members, key);
return commandManager.submitNewCommand(
SMIsMember, arguments, response -> castArray(handleArrayResponse(response), Boolean.class));
}

@Override
public CompletableFuture<Long> sdiffstore(@NonNull String destination, @NonNull String[] keys) {
String[] arguments = ArrayUtils.addFirst(keys, destination);
Expand Down
16 changes: 16 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 @@ -76,6 +76,22 @@ public interface SetBaseCommands {
*/
CompletableFuture<Long> scard(String key);

/**
* Checks whether each member is contained in the members of the set stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/smismember/">redis.io</a> for details.
* @param key The key of the set to check.
* @param members A list of members to check for existence in the set.
* @return An <code>array</code> of <code>Boolean</code> values, each indicating if the respective
* member exists in the set.
* @example
* <pre>{@code
* Boolean[] areMembers = client.smismembmer("my_set", new String[] { "a", "b", "c" }).get();
* assert areMembers[0] && areMembers[1] && !areMembers[2]; // Only first two elements are present in "my_set"
* }</pre>
*/
CompletableFuture<Boolean[]> smismember(String key, String[] members);

/**
* Moves <code>member</code> from the set at <code>source</code> to the set at <code>destination
* </code>, removing it from the source set. Creates a new destination set if needed. The
Expand Down
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 @@ -68,6 +68,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SInter;
import static redis_request.RedisRequestOuterClass.RequestType.SInterStore;
import static redis_request.RedisRequestOuterClass.RequestType.SIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SMove;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
Expand Down Expand Up @@ -930,6 +931,21 @@ public T scard(@NonNull String key) {
return getThis();
}

/**
* Checks whether each member is contained in the members of the set stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/smismember/">redis.io</a> for details.
* @param key The key of the set to check.
* @param members A list of members to check for existence in the set.
* @return Command Response - An <code>array</code> of <code>Boolean</code> values, each
* indicating if the respective member exists in the set.
*/
public T smismember(@NonNull String key, @NonNull String[] members) {
ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(members, key));
protobufTransaction.addCommands(buildCommand(SMIsMember, commandArgs));
return getThis();
}

/**
* Stores the difference between the first set and all the successive sets in <code>keys</code>
* into a new set at <code>destination</code>.
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 @@ -88,6 +88,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SInter;
import static redis_request.RedisRequestOuterClass.RequestType.SInterStore;
import static redis_request.RedisRequestOuterClass.RequestType.SIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SMove;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
Expand Down Expand Up @@ -1736,6 +1737,31 @@ public void scard_returns_success() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void smismember_returns_success() {
// setup
String key = "testKey";
String[] members = {"1", "2"};
String[] arguments = {"testKey", "1", "2"};
Boolean[] value = {true, false};

CompletableFuture<Boolean[]> testResponse = new CompletableFuture<>();
testResponse.complete(value);

// match on protobuf request
when(commandManager.<Boolean[]>submitNewCommand(eq(SMIsMember), eq(arguments), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Boolean[]> response = service.smismember(key, members);
Boolean[] payload = response.get();

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

@SneakyThrows
@Test
public void sdiffstore_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SInter;
import static redis_request.RedisRequestOuterClass.RequestType.SInterStore;
import static redis_request.RedisRequestOuterClass.RequestType.SIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMIsMember;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SMove;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
Expand Down Expand Up @@ -286,6 +287,9 @@ 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.smismember("key", new String[] {"1", "2"});
results.add(Pair.of(SMIsMember, buildArgs("key", "1", "2")));

transaction.sunionstore("key", new String[] {"set1", "set2"});
results.add(
Pair.of(
Expand Down
23 changes: 23 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,29 @@ public void sinterstore(BaseClient client) {
assertTrue(executionException.getCause() instanceof RequestException);
}

@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
public void smismember(BaseClient client) {
String key1 = UUID.randomUUID().toString();
String key2 = UUID.randomUUID().toString();

assertEquals(2, client.sadd(key1, new String[] {"one", "two"}).get());
assertArrayEquals(
new Boolean[] {true, false}, client.smismember(key1, new String[] {"one", "three"}).get());

// empty set
assertArrayEquals(
new Boolean[] {false, false}, client.smismember(key2, new String[] {"one", "three"}).get());

// Key exists, but it is not a set
assertEquals(OK, client.set(key2, "value").get());
ExecutionException executionException =
assertThrows(
ExecutionException.class, () -> client.smismember(key2, new String[] {"_"}).get());
assertInstanceOf(RequestException.class, executionException.getCause());
}

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

baseTransaction.sadd(setKey2, new String[] {"a", "b"});
Expand Down Expand Up @@ -217,6 +218,7 @@ public static Object[] transactionTestResult() {
1L,
true, // sismember(key7, "baz")
Set.of("baz"), // smembers(key7)
new Boolean[] {true, false}, // smismembmer(key7, new String[] {"baz", "foo"})
Set.of("baz"), // sinter(new String[] { key7, key7 })
2L, // sadd(setKey2, new String[] { "a", "b" })
3L, // sunionstore(setKey3, new String[] { setKey2, key7 })
Expand Down

0 comments on commit 3b29627

Please sign in to comment.