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: Add SETRANGE command. #1235

Merged
merged 8 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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 @@ -135,6 +135,7 @@ enum RequestType {
Rename = 91;
DBSize = 92;
Brpop = 93;
SetRange = 107;
}

message Command {
Expand Down
1 change: 1 addition & 0 deletions glide-core/src/socket_listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ fn get_command(request: &Command) -> Option<Cmd> {
RequestType::Rename => Some(cmd("RENAME")),
RequestType::DBSize => Some(cmd("DBSIZE")),
RequestType::Brpop => Some(cmd("BRPOP")),
RequestType::SetRange => Some(cmd("SETRANGE")),
}
}

Expand Down
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 @@ -42,6 +42,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SetRange;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
import static redis_request.RedisRequestOuterClass.RequestType.TTL;
Expand Down Expand Up @@ -319,6 +320,12 @@ public CompletableFuture<Long> strlen(@NonNull String key) {
return commandManager.submitNewCommand(Strlen, new String[] {key}, this::handleLongResponse);
}

@Override
public CompletableFuture<Long> setrange(@NonNull String key, int offset, @NonNull String value) {
return commandManager.submitNewCommand(
SetRange, new String[] {key, Integer.toString(offset), value}, this::handleLongResponse);
}

@Override
public CompletableFuture<String> hget(@NonNull String key, @NonNull String field) {
return commandManager.submitNewCommand(
Expand Down
21 changes: 21 additions & 0 deletions java/client/src/main/java/glide/api/commands/StringCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,25 @@ public interface StringCommands {
* }</pre>
*/
CompletableFuture<Long> strlen(String key);

/**
* Overwrites part of the string stored at <code>key</code>, starting at the specified <code>
* offset</code>, for the entire length of <code>value</code>.<br>
* If the <code>offset</code> is larger than the current length of the string at <code>key</code>,
* the string is padded with zero-bytes to make <code>offset</code> fit. Creates the key if it
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* doesn't exist.
*
* @see <a href="https://redis.io/commands/setrange/">redis.io</a> for details.
* @param key The key of the string to update.
* @param offset The position in the string where <code>value</code> should be written.
* @param value The string which should be written at the position specified by <code>offset
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* </code>.
* @return The length of the string stored at <code>key</code> after it was modified.
* @example
* <pre>{@code
* long len = client.setrange("key", 6, "Redis").get();
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* assert len == 11L;
* }</pre>
*/
CompletableFuture<Long> setrange(String key, int offset, String value);
}
22 changes: 22 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 @@ -50,6 +50,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SetRange;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
import static redis_request.RedisRequestOuterClass.RequestType.TTL;
Expand Down Expand Up @@ -379,6 +380,27 @@ public T strlen(@NonNull String key) {
return getThis();
}

/**
* Overwrites part of the string stored at <code>key</code>, starting at the specified <code>
* offset</code>, for the entire length of <code>value</code>.<br>
* If the <code>offset</code> is larger than the current length of the string at <code>key</code>,
* the string is padded with zero-bytes to make <code>offset</code> fit. Creates the key if it
* doesn't exist.
*
* @see <a href="https://redis.io/commands/setrange/">redis.io</a> for details.
* @param key The key of the string to update.
* @param offset The position in the string where <code>value</code> should be written.
* @param value The string which should be written at the position specified by <code>offset
* </code>.
* @return Command Response - The length of the string stored at <code>key</code> after it was
* modified.
*/
public T setrange(@NonNull String key, int offset, @NonNull String value) {
ArgsArray commandArgs = buildArgs(key, Integer.toString(offset), value);
protobufTransaction.addCommands(buildCommand(SetRange, commandArgs));
return getThis();
}

/**
* Retrieve the value associated with <code>field</code> in the hash stored at <code>key</code>.
*
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 @@ -62,6 +62,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.Select;
import static redis_request.RedisRequestOuterClass.RequestType.SetRange;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
import static redis_request.RedisRequestOuterClass.RequestType.TTL;
Expand Down Expand Up @@ -938,6 +939,32 @@ public void strlen_returns_success() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void setrange_returns_success() {
// setup
String key = "testKey";
int offset = 42;
String str = "pewpew";
String[] arguments = new String[] {key, Integer.toString(offset), str};
Long value = 10L;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: value -> responseValue (value is confusing in the key-value context)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 99 tests in this file and almost all of them have variable named "value".
I'm going to refactor tests in another PR (and rename the var). Is it OK for you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commandResult or futureResult is even less ambiguous.


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

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

// exercise
CompletableFuture<Long> response = service.setrange(key, offset, str);
Long payload = response.get();

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

@SneakyThrows
@Test
public void hget_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SMembers;
import static redis_request.RedisRequestOuterClass.RequestType.SRem;
import static redis_request.RedisRequestOuterClass.RequestType.SetRange;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
import static redis_request.RedisRequestOuterClass.RequestType.TTL;
Expand Down Expand Up @@ -149,6 +150,11 @@ public void transaction_builds_protobuf_request(BaseTransaction<?> transaction)
transaction.strlen("key");
results.add(Pair.of(Strlen, ArgsArray.newBuilder().addArgs("key").build()));

transaction.setrange("key", 42, "str");
results.add(
Pair.of(
SetRange, ArgsArray.newBuilder().addArgs("key").addArgs("42").addArgs("str").build()));

transaction.hset("key", Map.of("field", "value"));
results.add(
Pair.of(
Expand Down
28 changes: 28 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,34 @@ public void strlen(BaseClient client) {
assertTrue(exception.getCause() instanceof RequestException);
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
public void setrange(BaseClient client) {
String stringKey = UUID.randomUUID().toString();
String nonStringKey = UUID.randomUUID().toString();
String nonExistingKey = UUID.randomUUID().toString();

assertEquals(OK, client.set(stringKey, "Hello world").get());
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
assertEquals(11L, client.setrange(stringKey, 6, "GLIDE").get());
assertEquals("Hello GLIDE", client.get(stringKey).get());
// offset > len
assertEquals(20L, client.setrange(stringKey, 15, "GLIDE").get());
assertEquals("Hello GLIDE\0\0\0\0GLIDE", client.get(stringKey).get());
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
// new key
assertEquals(5L, client.setrange(nonExistingKey, 0, "GLIDE").get());
assertEquals("GLIDE", client.get(nonExistingKey).get());

assertEquals(1, client.lpush(nonStringKey, new String[] {"_"}).get());
Exception exception =
assertThrows(ExecutionException.class, () -> client.setrange(nonStringKey, 0, "_").get());
assertTrue(exception.getCause() instanceof RequestException);
exception =
assertThrows(
ExecutionException.class, () -> client.setrange("foo", Integer.MAX_VALUE, "_").get());
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
assertTrue(exception.getCause() instanceof RequestException);
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public static BaseTransaction<?> transactionTest(BaseTransaction<?> baseTransact
baseTransaction.incrByFloat(key3, 0.5);

baseTransaction.unlink(new String[] {key3});
baseTransaction.setrange(key3, 0, "GLIDE");

baseTransaction.hset(key4, Map.of(field1, value1, field2, value2));
baseTransaction.hget(key4, field1);
Expand Down Expand Up @@ -124,6 +125,7 @@ public static Object[] transactionTestResult() {
0L,
0.5,
1L,
5L, // setrange(key3, 0, "GLIDE")
2L,
value1,
true,
Expand Down