Skip to content

Commit

Permalink
Java: Adding command MSetNX (#1551)
Browse files Browse the repository at this point in the history
* Java: Adding command MSetNX

Co-authored-by: TJ Zhang <[email protected]>
Co-authored-by: Yury-Fridlyand <[email protected]>
  • Loading branch information
3 people authored Jun 11, 2024
1 parent 24fb145 commit bd233de
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 4 deletions.
2 changes: 1 addition & 1 deletion glide-core/src/client/value_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
}),
b"INCRBYFLOAT" | b"HINCRBYFLOAT" | b"ZINCRBY" => Some(ExpectedReturnType::Double),
b"HEXISTS" | b"HSETNX" | b"EXPIRE" | b"EXPIREAT" | b"PEXPIRE" | b"PEXPIREAT"
| b"SISMEMBER" | b"PERSIST" | b"SMOVE" | b"RENAMENX" | b"MOVE" | b"COPY" => {
| b"SISMEMBER" | b"PERSIST" | b"SMOVE" | b"RENAMENX" | b"MOVE" | b"COPY" | b"MSETNX" => {
Some(ExpectedReturnType::Boolean)
}
b"SMISMEMBER" => Some(ExpectedReturnType::ArrayOfBools),
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 @@ -213,6 +213,7 @@ enum RequestType {
SInterCard = 175;
XRevRange = 176;
Copy = 178;
MSetNX = 179;
}

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 @@ -183,6 +183,7 @@ pub enum RequestType {
SInterCard = 175,
XRevRange = 176,
Copy = 178,
MSetNX = 179,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -369,6 +370,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::Copy => RequestType::Copy,
ProtobufRequestType::Sort => RequestType::Sort,
ProtobufRequestType::XRevRange => RequestType::XRevRange,
ProtobufRequestType::MSetNX => RequestType::MSetNX,
}
}
}
Expand Down Expand Up @@ -551,6 +553,7 @@ impl RequestType {
RequestType::Copy => Some(cmd("COPY")),
RequestType::Sort => Some(cmd("SORT")),
RequestType::XRevRange => Some(cmd("XREVRANGE")),
RequestType::MSetNX => Some(cmd("MSETNX")),
}
}
}
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 @@ -74,6 +74,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.LTrim;
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
import static redis_request.RedisRequestOuterClass.RequestType.MSetNX;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectEncoding;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectFreq;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectIdleTime;
Expand Down Expand Up @@ -1797,4 +1798,10 @@ public CompletableFuture<Boolean> copy(@NonNull String source, @NonNull String d
String[] arguments = new String[] {source, destination};
return commandManager.submitNewCommand(Copy, arguments, this::handleBooleanResponse);
}

@Override
public CompletableFuture<Boolean> msetnx(@NonNull Map<String, String> keyValueMap) {
String[] args = convertMapToKeyValueStringArray(keyValueMap);
return commandManager.submitNewCommand(MSetNX, args, this::handleBooleanResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,23 @@ public interface StringBaseCommands {
*/
CompletableFuture<String> mset(Map<String, String> keyValueMap);

/**
* Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or
* more keys already exist, the entire operation fails.
*
* @apiNote When in cluster mode, all keys in <code>keyValueMap</code> must map to the same hash
* slot.
* @see <a href="https://redis.io/commands/msetnx/">redis.io</a> for details.
* @param keyValueMap A key-value map consisting of keys and their respective values to set.
* @return <code>true</code> if all keys were set. <code>false</code> if no key was set.
* @example
* <pre>{@code
* Boolean result = client.msetnx(Map.of("key1", "value1", "key2", "value2"}).get();
* assert result;
* }</pre>
*/
CompletableFuture<Boolean> msetnx(Map<String, String> keyValueMap);

/**
* Increments the number stored at <code>key</code> by one. If <code>key</code> does not exist, it
* is set to 0 before performing the operation.
Expand Down
18 changes: 18 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 @@ -97,6 +97,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Lolwut;
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
import static redis_request.RedisRequestOuterClass.RequestType.MSetNX;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectEncoding;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectFreq;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectIdleTime;
Expand Down Expand Up @@ -457,6 +458,23 @@ public T mset(@NonNull Map<String, String> keyValueMap) {
return getThis();
}

/**
* Sets multiple keys to multiple values in a single operation. Performs no operation at all even
* if just a single key already exists.
*
* @see <a href="https://redis.io/commands/msetnx/">redis.io</a> for details.
* @param keyValueMap A key-value map consisting of keys and their respective values to set.
* @return Command Response - <code>true</code> if all keys were set, <code>false</code> if no key
* was set.
*/
public T msetnx(@NonNull Map<String, String> keyValueMap) {
String[] args = convertMapToKeyValueStringArray(keyValueMap);
ArgsArray commandArgs = buildArgs(args);

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

/**
* Increments the number stored at <code>key</code> by one. If <code>key</code> does not exist, it
* is set to 0 before performing the operation.
Expand Down
27 changes: 27 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Lolwut;
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
import static redis_request.RedisRequestOuterClass.RequestType.MSetNX;
import static redis_request.RedisRequestOuterClass.RequestType.Move;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectEncoding;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectFreq;
Expand Down Expand Up @@ -1057,6 +1058,32 @@ public void mset_returns_success() {
assertEquals(OK, payload);
}

@SneakyThrows
@Test
public void msetnx_returns_success() {
// setup
Map<String, String> keyValueMap = new LinkedHashMap<>();
keyValueMap.put("key1", "value1");
keyValueMap.put("key2", "value2");
String[] args = {"key1", "value1", "key2", "value2"};
Boolean value = true;

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

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

// exercise
CompletableFuture<Boolean> response = service.msetnx(keyValueMap);
Boolean payload = response.get();

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

@SneakyThrows
@Test
public void incr_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Lolwut;
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
import static redis_request.RedisRequestOuterClass.RequestType.MSetNX;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectEncoding;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectFreq;
import static redis_request.RedisRequestOuterClass.RequestType.ObjectIdleTime;
Expand Down Expand Up @@ -278,6 +279,9 @@ public void transaction_builds_protobuf_request(BaseTransaction<?> transaction)
transaction.mset(Map.of("key", "value"));
results.add(Pair.of(MSet, buildArgs("key", "value")));

transaction.msetnx(Map.of("key", "value"));
results.add(Pair.of(MSetNX, buildArgs("key", "value")));

transaction.mget(new String[] {"key"});
results.add(Pair.of(MGet, buildArgs("key")));

Expand Down
24 changes: 24 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -5133,4 +5133,28 @@ public void copy(BaseClient client) {
assertTrue(client.copy(source, destination, true).get());
assertEquals("two", client.get(destination).get());
}

@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
public void msetnx(BaseClient client) {
// keys are from different slots
String key1 = "{key}-1" + UUID.randomUUID();
String key2 = "{key}-2" + UUID.randomUUID();
String key3 = "{key}-3" + UUID.randomUUID();
String nonExisting = UUID.randomUUID().toString();
String value = UUID.randomUUID().toString();
Map<String, String> keyValueMap1 = Map.of(key1, value, key2, value);
Map<String, String> keyValueMap2 = Map.of(key2, value, key3, value);

// all keys are empty, successfully set
assertTrue(client.msetnx(keyValueMap1).get());
assertArrayEquals(
new String[] {value, value, null},
client.mget(new String[] {key1, key2, nonExisting}).get());

// one of the keys is already set, nothing gets set
assertFalse(client.msetnx(keyValueMap2).get());
assertNull(client.get(key3).get());
}
}
16 changes: 14 additions & 2 deletions java/integTest/src/test/java/glide/TransactionTestUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ private static Object[] stringCommands(BaseTransaction<?> transaction) {
String stringKey1 = "{StringKey}-1-" + UUID.randomUUID();
String stringKey2 = "{StringKey}-2-" + UUID.randomUUID();
String stringKey3 = "{StringKey}-3-" + UUID.randomUUID();
String stringKey4 = "{StringKey}-4-" + UUID.randomUUID();
String stringKey5 = "{StringKey}-5-" + UUID.randomUUID();

transaction
.set(stringKey1, value1)
Expand All @@ -215,7 +217,12 @@ private static Object[] stringCommands(BaseTransaction<?> transaction) {
.decrBy(stringKey3, 2)
.incrByFloat(stringKey3, 0.5)
.setrange(stringKey3, 0, "GLIDE")
.getrange(stringKey3, 0, 5);
.getrange(stringKey3, 0, 5)
.msetnx(Map.of(stringKey4, "foo", stringKey5, "bar"))
.mget(new String[] {stringKey4, stringKey5})
.del(new String[] {stringKey5})
.msetnx(Map.of(stringKey4, "foo", stringKey5, "bar"))
.mget(new String[] {stringKey4, stringKey5});

return new Object[] {
OK, // set(stringKey1, value1)
Expand All @@ -232,7 +239,12 @@ private static Object[] stringCommands(BaseTransaction<?> transaction) {
0L, // decrBy(stringKey3, 2)
0.5, // incrByFloat(stringKey3, 0.5)
5L, // setrange(stringKey3, 0, "GLIDE")
"GLIDE" // getrange(stringKey3, 0, 5)
"GLIDE", // getrange(stringKey3, 0, 5)
true, // msetnx(Map.of(stringKey4, "foo", stringKey5, "bar"))
new String[] {"foo", "bar"}, // mget({stringKey4, stringKey5})
1L, // del(stringKey5)
false, // msetnx(Map.of(stringKey4, "foo", stringKey5, "bar"))
new String[] {"foo", null}, // mget({stringKey4, stringKey5})
};
}

Expand Down
3 changes: 2 additions & 1 deletion java/integTest/src/test/java/glide/cluster/CommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,8 @@ public static Stream<Arguments> callCrossSlotCommandsWhichShouldFail() {
clusterClient.fcall("func", new String[] {"abc", "zxy", "lkn"}, new String[0])),
Arguments.of(
"xread", null, clusterClient.xread(Map.of("abc", "stream1", "zxy", "stream2"))),
Arguments.of("copy", "6.2.0", clusterClient.copy("abc", "def", true)));
Arguments.of("copy", "6.2.0", clusterClient.copy("abc", "def", true)),
Arguments.of("msetnx", null, clusterClient.msetnx(Map.of("abc", "def", "ghi", "jkl"))));
}

@SneakyThrows
Expand Down

0 comments on commit bd233de

Please sign in to comment.