Skip to content

Commit

Permalink
Java: add GETEX (valkey-io#1609)
Browse files Browse the repository at this point in the history
* implement getex options

* current progress

* continue progress on tests

* implement getex

* Address comments

* address more comments

* fix tests

* address final comments

* address remaining comments

* resolve merge conflicts

* ran spotless

* add new tests

* fix tests

* Add initial test for GetExOptions

Signed-off-by: Andrew Carbonetto <[email protected]>

* fix tests

* fix tests

* ran spotless

---------

Signed-off-by: Andrew Carbonetto <[email protected]>
Co-authored-by: Chloe Yip <[email protected]>
Co-authored-by: Chloe Yip <[email protected]>
Co-authored-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
4 people committed Jun 24, 2024
1 parent 2b1cc79 commit b46e528
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 0 deletions.
14 changes: 14 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Get;
import static redis_request.RedisRequestOuterClass.RequestType.GetBit;
import static redis_request.RedisRequestOuterClass.RequestType.GetDel;
import static redis_request.RedisRequestOuterClass.RequestType.GetEx;
import static redis_request.RedisRequestOuterClass.RequestType.GetRange;
import static redis_request.RedisRequestOuterClass.RequestType.HDel;
import static redis_request.RedisRequestOuterClass.RequestType.HExists;
Expand Down Expand Up @@ -174,6 +175,7 @@
import glide.api.models.GlideString;
import glide.api.models.Script;
import glide.api.models.commands.ExpireOptions;
import glide.api.models.commands.GetExOptions;
import glide.api.models.commands.LInsertOptions.InsertPosition;
import glide.api.models.commands.LPosOptions;
import glide.api.models.commands.ListDirection;
Expand Down Expand Up @@ -506,6 +508,18 @@ public CompletableFuture<String> getdel(@NonNull String key) {
GetDel, new String[] {key}, this::handleStringOrNullResponse);
}

@Override
public CompletableFuture<String> getex(@NonNull String key) {
return commandManager.submitNewCommand(
GetEx, new String[] {key}, this::handleStringOrNullResponse);
}

@Override
public CompletableFuture<String> getex(@NonNull String key, @NonNull GetExOptions options) {
String[] arguments = ArrayUtils.addFirst(options.toArgs(), key);
return commandManager.submitNewCommand(GetEx, arguments, this::handleStringOrNullResponse);
}

@Override
public CompletableFuture<GlideString> getdel(@NonNull GlideString key) {
return commandManager.submitNewCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package glide.api.commands;

import glide.api.models.GlideString;
import glide.api.models.commands.GetExOptions;
import glide.api.models.commands.SetOptions;
import glide.api.models.commands.SetOptions.ConditionalSet;
import glide.api.models.commands.SetOptions.SetOptionsBuilder;
Expand Down Expand Up @@ -75,6 +76,41 @@ public interface StringBaseCommands {
*/
CompletableFuture<String> getdel(String key);

/**
* Gets the value associated with the given <code>key</code>.
*
* @since Redis 6.2.0.
* @see <a href="https://redis.io/docs/latest/commands/getex/">redis.io</a> for details.
* @param key The <code>key</code> to retrieve from the database.
* @return If <code>key</code> exists, return the <code>value</code> of the <code>key</code>.
* Otherwise, return <code>null</code>.
* @example
* <pre>{@code
* String value = client.getex("key").get();
* assert value.equals("value");
* }</pre>
*/
CompletableFuture<String> getex(String key);

/**
* Gets the value associated with the given <code>key</code>.
*
* @since Redis 6.2.0.
* @see <a href="https://redis.io/docs/latest/commands/getex/">redis.io</a> for details.
* @param key The <code>key</code> to retrieve from the database.
* @param options The {@link GetExOptions} options.
* @return If <code>key</code> exists, return the <code>value</code> of the <code>key</code>.
* Otherwise, return <code>null</code>.
* @example
* <pre>{@code
* String response = client.set("key", "value").get();
* assert response.equals(OK);
* String value = client.getex("key", GetExOptions.Seconds(10L)).get();
* assert value.equals("value");
* }</pre>
*/
CompletableFuture<String> getex(String key, GetExOptions options);

/**
* Gets a string value associated with the given <code>key</code> and deletes the key.
*
Expand Down
33 changes: 33 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 @@ -66,6 +66,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Get;
import static redis_request.RedisRequestOuterClass.RequestType.GetBit;
import static redis_request.RedisRequestOuterClass.RequestType.GetDel;
import static redis_request.RedisRequestOuterClass.RequestType.GetEx;
import static redis_request.RedisRequestOuterClass.RequestType.GetRange;
import static redis_request.RedisRequestOuterClass.RequestType.HDel;
import static redis_request.RedisRequestOuterClass.RequestType.HExists;
Expand Down Expand Up @@ -190,6 +191,7 @@
import com.google.protobuf.ByteString;
import glide.api.models.commands.ExpireOptions;
import glide.api.models.commands.FlushMode;
import glide.api.models.commands.GetExOptions;
import glide.api.models.commands.InfoOptions;
import glide.api.models.commands.InfoOptions.Section;
import glide.api.models.commands.LInsertOptions.InsertPosition;
Expand Down Expand Up @@ -392,6 +394,37 @@ public T getdel(@NonNull String key) {
return getThis();
}

/**
* Gets the value associated with the given <code>key</code>.
*
* @since Redis 6.2.0.
* @see <a href="https://redis.io/docs/latest/commands/getex/">redis.io</a> for details.
* @param key The <code>key</code> to retrieve from the database.
* @return Command Response - If <code>key</code> exists, return the <code>value</code> of the
* <code>key</code>. Otherwise, return <code>null</code>.
*/
public T getex(@NonNull String key) {
ArgsArray commandArgs = buildArgs(key);
protobufTransaction.addCommands(buildCommand(GetEx, commandArgs));
return getThis();
}

/**
* Gets the value associated with the given <code>key</code>.
*
* @since Redis 6.2.0.
* @see <a href="https://redis.io/docs/latest/commands/getex/">redis.io</a> for details.
* @param key The <code>key</code> to retrieve from the database.
* @param options The {@link GetExOptions} options.
* @return Command Response - If <code>key</code> exists, return the <code>value</code> of the
* <code>key</code>. Otherwise, return <code>null</code>.
*/
public T getex(@NonNull String key, @NonNull GetExOptions options) {
ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(options.toArgs(), key));
protobufTransaction.addCommands(buildCommand(GetEx, commandArgs));
return getThis();
}

/**
* Sets the given key with the given value.
*
Expand Down
111 changes: 111 additions & 0 deletions java/client/src/main/java/glide/api/models/commands/GetExOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.models.commands;

import static glide.api.models.commands.GetExOptions.ExpiryType.MILLISECONDS;
import static glide.api.models.commands.GetExOptions.ExpiryType.PERSIST;
import static glide.api.models.commands.GetExOptions.ExpiryType.SECONDS;
import static glide.api.models.commands.GetExOptions.ExpiryType.UNIX_MILLISECONDS;
import static glide.api.models.commands.GetExOptions.ExpiryType.UNIX_SECONDS;

import glide.api.commands.StringBaseCommands;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;

/**
* Optional arguments to {@link StringBaseCommands#getex(String, GetExOptions)} command.
*
* @see <a href="https://redis.io/docs/latest/commands/getex/">redis.io</a>
*/
public class GetExOptions {

/** Expiry type for the time to live */
private final ExpiryType type;

/** The amount of time to live before the key expires. */
private Long count;

private GetExOptions(ExpiryType type) {
this.type = type;
}

private GetExOptions(ExpiryType type, Long count) {
this.type = type;
this.count = count;
}

/**
* Set the specified expire time, in seconds. Equivalent to <code>EX</code> in the Redis API.
*
* @param seconds The time to expire, in seconds.
* @return The options specifying the given expiry.
*/
public static GetExOptions Seconds(Long seconds) {
return new GetExOptions(SECONDS, seconds);
}

/**
* Set the specified expire time, in milliseconds. Equivalent to <code>PX</code> in the Redis API.
*
* @param milliseconds The time to expire, in milliseconds.
* @return The options specifying the given expiry.
*/
public static GetExOptions Milliseconds(Long milliseconds) {
return new GetExOptions(MILLISECONDS, milliseconds);
}

/**
* Set the specified Unix time at which the key will expire, in seconds. Equivalent to <code>
* EXAT</code> in the Redis API.
*
* @param unixSeconds The <code>UNIX TIME</code> to expire, in seconds.
* @return The options specifying the given expiry.
*/
public static GetExOptions UnixSeconds(Long unixSeconds) {
return new GetExOptions(UNIX_SECONDS, unixSeconds);
}

/**
* Set the specified Unix time at which the key will expire, in milliseconds. Equivalent to <code>
* PXAT</code> in the Redis API.
*
* @param unixMilliseconds The <code>UNIX TIME</code> to expire, in milliseconds.
* @return The options specifying the given expiry.
*/
public static GetExOptions UnixMilliseconds(Long unixMilliseconds) {
return new GetExOptions(UNIX_MILLISECONDS, unixMilliseconds);
}

/** Remove the time to live associated with the key. */
public static GetExOptions Persist() {
return new GetExOptions(PERSIST);
}

/** Types of value expiration configuration. */
@RequiredArgsConstructor
protected enum ExpiryType {
SECONDS("EX"),
MILLISECONDS("PX"),
UNIX_SECONDS("EXAT"),
UNIX_MILLISECONDS("PXAT"),
PERSIST("PERSIST");

private final String redisApi;
}

/**
* Converts GetExOptions into a String[] to pass to the <code>GETEX</code> command.
*
* @return String[]
*/
public String[] toArgs() {
List<String> optionArgs = new ArrayList<>();

optionArgs.add(type.redisApi);
if (count != null) {
optionArgs.add(String.valueOf(count));
}
System.out.println(optionArgs);
return optionArgs.toArray(new String[0]);
}
}
54 changes: 54 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Get;
import static redis_request.RedisRequestOuterClass.RequestType.GetBit;
import static redis_request.RedisRequestOuterClass.RequestType.GetDel;
import static redis_request.RedisRequestOuterClass.RequestType.GetEx;
import static redis_request.RedisRequestOuterClass.RequestType.GetRange;
import static redis_request.RedisRequestOuterClass.RequestType.HDel;
import static redis_request.RedisRequestOuterClass.RequestType.HExists;
Expand Down Expand Up @@ -232,6 +233,7 @@
import glide.api.models.commands.ConditionalChange;
import glide.api.models.commands.ExpireOptions;
import glide.api.models.commands.FlushMode;
import glide.api.models.commands.GetExOptions;
import glide.api.models.commands.InfoOptions;
import glide.api.models.commands.LPosOptions;
import glide.api.models.commands.ListDirection;
Expand Down Expand Up @@ -527,6 +529,58 @@ public void getdel() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void getex() {
// setup
String key = "testKey";
String value = "testValue";
CompletableFuture<String> testResponse = new CompletableFuture<>();
testResponse.complete(value);
when(commandManager.<String>submitNewCommand(eq(GetEx), eq(new String[] {key}), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<String> response = service.getex(key);
String payload = response.get();

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

private static List<Arguments> getGetExOptions() {
return List.of(
Arguments.of(
// seconds
"test_with_seconds", GetExOptions.Seconds(10L), new String[] {"EX", "10"}),
Arguments.of(
// milliseconds
"test_with_milliseconds",
GetExOptions.Milliseconds(1000L),
new String[] {"PX", "1000"}),
Arguments.of(
// unix seconds
"test_with_unix_seconds", GetExOptions.UnixSeconds(10L), new String[] {"EXAT", "10"}),
Arguments.of(
// unix milliseconds
"test_with_unix_milliseconds",
GetExOptions.UnixMilliseconds(1000L),
new String[] {"PXAT", "1000"}),
Arguments.of(
// persist
"test_with_persist", GetExOptions.Persist(), new String[] {"PERSIST"}));
}

@SneakyThrows
@ParameterizedTest(name = "{0}")
@MethodSource("getGetExOptions")
public void getex_options(String testName, GetExOptions options, String[] expectedArgs) {
assertArrayEquals(
expectedArgs, options.toArgs(), "Expected " + testName + " toArgs() to pass.");
System.out.println(expectedArgs);
}

@SneakyThrows
@Test
public void set_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Get;
import static redis_request.RedisRequestOuterClass.RequestType.GetBit;
import static redis_request.RedisRequestOuterClass.RequestType.GetDel;
import static redis_request.RedisRequestOuterClass.RequestType.GetEx;
import static redis_request.RedisRequestOuterClass.RequestType.GetRange;
import static redis_request.RedisRequestOuterClass.RequestType.HDel;
import static redis_request.RedisRequestOuterClass.RequestType.HExists;
Expand Down Expand Up @@ -205,6 +206,7 @@

import com.google.protobuf.ByteString;
import glide.api.models.commands.ConditionalChange;
import glide.api.models.commands.GetExOptions;
import glide.api.models.commands.InfoOptions;
import glide.api.models.commands.LPosOptions;
import glide.api.models.commands.ListDirection;
Expand Down Expand Up @@ -268,6 +270,12 @@ public void transaction_builds_protobuf_request(BaseTransaction<?> transaction)
transaction.get("key");
results.add(Pair.of(Get, buildArgs("key")));

transaction.getex("key");
results.add(Pair.of(GetEx, buildArgs("key")));

transaction.getex("key", GetExOptions.Seconds(10L));
results.add(Pair.of(GetEx, buildArgs("key", "EX", "10")));

transaction.set("key", "value");
results.add(Pair.of(Set, buildArgs("key", "value")));

Expand Down
Loading

0 comments on commit b46e528

Please sign in to comment.