Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java: Adding command MSetNX #1551

Merged
merged 3 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -212,6 +212,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 @@ -182,6 +182,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 @@ -367,6 +368,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 @@ -548,6 +550,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 @@ -73,6 +73,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 @@ -1786,4 +1787,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();
* assertTrue(result);
tjzhang-BQ marked this conversation as resolved.
Show resolved Hide resolved
* }</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 @@ -96,6 +96,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 @@ -456,6 +457,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
tjzhang-BQ marked this conversation as resolved.
Show resolved Hide resolved
* 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 @@ -128,6 +128,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 @@ -1056,6 +1057,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 @@ -109,6 +109,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 @@ -277,6 +278,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());
tjzhang-BQ marked this conversation as resolved.
Show resolved Hide resolved
}
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,8 @@ public static Stream<Arguments> callCrossSlotCommandsWhichShouldFail() {
"sintercard", "7.0.0", clusterClient.sintercard(new String[] {"abc", "def"}, 1)),
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
Loading