Skip to content

Commit

Permalink
Java: Add EXPIRETIME and PEXPIRETIME commands. (valkey-io#1462)
Browse files Browse the repository at this point in the history
* Java: Add `EXPIRETIME` and `PEXPIRETIME` commands. (#300)

* Add `EXPIRETIME` and `PEXPIRETIME` commands.

Signed-off-by: Yury-Fridlyand <[email protected]>
Co-authored-by: Guian Gumpac <[email protected]>

* Update expiretime docs

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

* Update docs for expiretime

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

* Apply suggestions from code review

---------

Signed-off-by: Yury-Fridlyand <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Co-authored-by: Guian Gumpac <[email protected]>
Co-authored-by: Andrew Carbonetto <[email protected]>
Co-authored-by: Aaron <[email protected]>
  • Loading branch information
4 people authored and cyip10 committed Jun 24, 2024
1 parent 0cb42cf commit 6b5079a
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 8 deletions.
2 changes: 2 additions & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ enum RequestType {
HStrlen = 149;
FunctionLoad = 150;
LMPop = 155;
ExpireTime = 156;
PExpireTime = 157;
}

message Command {
Expand Down
6 changes: 6 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ pub enum RequestType {
HStrlen = 149,
FunctionLoad = 150,
LMPop = 155,
ExpireTime = 156,
PExpireTime = 157,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -325,6 +327,8 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::BitPos => RequestType::BitPos,
ProtobufRequestType::BitOp => RequestType::BitOp,
ProtobufRequestType::HStrlen => RequestType::HStrlen,
ProtobufRequestType::ExpireTime => RequestType::ExpireTime,
ProtobufRequestType::PExpireTime => RequestType::PExpireTime,
}
}
}
Expand Down Expand Up @@ -485,6 +489,8 @@ impl RequestType {
RequestType::BitPos => Some(cmd("BITPOS")),
RequestType::BitOp => Some(cmd("BITOP")),
RequestType::HStrlen => Some(cmd("HSTRLEN")),
RequestType::ExpireTime => Some(cmd("EXPIRETIME")),
RequestType::PExpireTime => Some(cmd("PEXPIRETIME")),
}
}
}
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 @@ -24,6 +24,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Exists;
import static redis_request.RedisRequestOuterClass.RequestType.Expire;
import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt;
import static redis_request.RedisRequestOuterClass.RequestType.ExpireTime;
import static redis_request.RedisRequestOuterClass.RequestType.GeoAdd;
import static redis_request.RedisRequestOuterClass.RequestType.GeoDist;
import static redis_request.RedisRequestOuterClass.RequestType.GeoHash;
Expand Down Expand Up @@ -66,6 +67,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ObjectRefCount;
import static redis_request.RedisRequestOuterClass.RequestType.PExpire;
import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt;
import static redis_request.RedisRequestOuterClass.RequestType.PExpireTime;
import static redis_request.RedisRequestOuterClass.RequestType.PTTL;
import static redis_request.RedisRequestOuterClass.RequestType.Persist;
import static redis_request.RedisRequestOuterClass.RequestType.PfAdd;
Expand Down Expand Up @@ -802,6 +804,18 @@ public CompletableFuture<Long> ttl(@NonNull String key) {
return commandManager.submitNewCommand(TTL, new String[] {key}, this::handleLongResponse);
}

@Override
public CompletableFuture<Long> expiretime(@NonNull String key) {
return commandManager.submitNewCommand(
ExpireTime, new String[] {key}, this::handleLongResponse);
}

@Override
public CompletableFuture<Long> pexpiretime(@NonNull String key) {
return commandManager.submitNewCommand(
PExpireTime, new String[] {key}, this::handleLongResponse);
}

@Override
public CompletableFuture<Object> invokeScript(@NonNull Script script) {
return commandManager.submitScript(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ CompletableFuture<Boolean> pexpireAt(
* @see <a href="https://redis.io/commands/ttl/">redis.io</a> for details.
* @param key The <code>key</code> to return its timeout.
* @return TTL in seconds, <code>-2</code> if <code>key</code> does not exist, or <code>-1</code>
* if <code>key</code> exists but has no associated expire.
* if <code>key</code> exists but has no associated expiration.
* @example
* <pre>{@code
* Long timeRemaining = client.ttl("my_key").get();
Expand All @@ -278,8 +278,43 @@ CompletableFuture<Boolean> pexpireAt(
*/
CompletableFuture<Long> ttl(String key);

// TODO move ScriptingAndFunctionsBaseCommands
// TODO add note about routing on cluster client
/**
* Returns the absolute Unix timestamp (since January 1, 1970) at which the given <code>key</code>
* will expire, in seconds.<br>
* To get the expiration with millisecond precision, use {@link #pexpiretime(String)}.
*
* @since Redis 7.0 and above.
* @see <a href="https://redis.io/commands/expiretime/">redis.io</a> for details.
* @param key The <code>key</code> to determine the expiration value of.
* @return The expiration Unix timestamp in seconds. <code>-2</code> if <code>key</code> does not
* exist, or <code>-1</code> if <code>key</code> exists but has no associated expiration.
* @example
* <pre>{@code
* Long expiration = client.expiretime("my_key").get();
* System.out.printf("The key expires at %d epoch time", expiration);
* }</pre>
*/
CompletableFuture<Long> expiretime(String key);

/**
* Returns the absolute Unix timestamp (since January 1, 1970) at which the given <code>key</code>
* will expire, in milliseconds.
*
* @since Redis 7.0 and above.
* @see <a href="https://redis.io/commands/pexpiretime/">redis.io</a> for details.
* @param key The <code>key</code> to determine the expiration value of.
* @return The expiration Unix timestamp in milliseconds. <code>-2</code> if <code>key</code> does
* not exist, or <code>-1</code> if <code>key</code> exists but has no associated expiration.
* @example
* <pre>{@code
* Long expiration = client.pexpiretime("my_key").get();
* System.out.printf("The key expires at %d epoch time (ms)", expiration);
* }</pre>
*/
CompletableFuture<Long> pexpiretime(String key);

// TODO move invokeScript to ScriptingAndFunctionsBaseCommands
// TODO add note to invokeScript about routing on cluster client
/**
* Invokes a Lua script.<br>
* This method simplifies the process of invoking scripts on a Redis server by using an object
Expand Down Expand Up @@ -365,7 +400,7 @@ CompletableFuture<Boolean> pexpireAt(
/**
* Returns the string representation of the type of the value stored at <code>key</code>.
*
* @see <a href="https://redis.io/commands/type/>redis.io</a> for details.
* @see <a href="https://redis.io/commands/type/">redis.io</a> for details.
* @param key The <code>key</code> to check its data type.
* @return If the <code>key</code> exists, the type of the stored value is returned. Otherwise, a
* "none" string is returned.
Expand Down
36 changes: 36 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 @@ -36,6 +36,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Exists;
import static redis_request.RedisRequestOuterClass.RequestType.Expire;
import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt;
import static redis_request.RedisRequestOuterClass.RequestType.ExpireTime;
import static redis_request.RedisRequestOuterClass.RequestType.FlushAll;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionLoad;
import static redis_request.RedisRequestOuterClass.RequestType.GeoAdd;
Expand Down Expand Up @@ -83,6 +84,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ObjectRefCount;
import static redis_request.RedisRequestOuterClass.RequestType.PExpire;
import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt;
import static redis_request.RedisRequestOuterClass.RequestType.PExpireTime;
import static redis_request.RedisRequestOuterClass.RequestType.PTTL;
import static redis_request.RedisRequestOuterClass.RequestType.Persist;
import static redis_request.RedisRequestOuterClass.RequestType.PfAdd;
Expand Down Expand Up @@ -1457,6 +1459,40 @@ public T ttl(@NonNull String key) {
return getThis();
}

/**
* Returns the absolute Unix timestamp (since January 1, 1970) at which the given <code>key</code>
* will expire, in seconds.<br>
* To get the expiration with millisecond precision, use {@link #pexpiretime(String)}.
*
* @since Redis 7.0 and above.
* @see <a href="https://redis.io/commands/expiretime/">redis.io</a> for details.
* @param key The <code>key</code> to determine the expiration value of.
* @return Command response - The expiration Unix timestamp in seconds, <code>-2</code> if <code>
* key</code> does not exist, or <code>-1</code> if <code>key</code> exists but has no
* associated expiration.
*/
public T expiretime(@NonNull String key) {
protobufTransaction.addCommands(buildCommand(ExpireTime, buildArgs(key)));
return getThis();
}

/**
* Returns the absolute Unix timestamp (since January 1, 1970) at which the given <code>key</code>
* will expire, in milliseconds.
*
* @since Redis 7.0 and above.
* @see <a href="https://redis.io/commands/pexpiretime/">redis.io</a> for details.
* @param key The <code>key</code> to determine the expiration value of.
* @return Command response - The expiration Unix timestamp in milliseconds, <code>-2</code> if
* <code>key
* </code> does not exist, or <code>-1</code> if <code>key</code> exists but has no associated
* expiration.
*/
public T pexpiretime(@NonNull String key) {
protobufTransaction.addCommands(buildCommand(PExpireTime, buildArgs(key)));
return getThis();
}

/**
* Gets the current connection id.
*
Expand Down
44 changes: 44 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Exists;
import static redis_request.RedisRequestOuterClass.RequestType.Expire;
import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt;
import static redis_request.RedisRequestOuterClass.RequestType.ExpireTime;
import static redis_request.RedisRequestOuterClass.RequestType.FlushAll;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionLoad;
import static redis_request.RedisRequestOuterClass.RequestType.GeoAdd;
Expand Down Expand Up @@ -104,6 +105,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ObjectRefCount;
import static redis_request.RedisRequestOuterClass.RequestType.PExpire;
import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt;
import static redis_request.RedisRequestOuterClass.RequestType.PExpireTime;
import static redis_request.RedisRequestOuterClass.RequestType.PTTL;
import static redis_request.RedisRequestOuterClass.RequestType.Persist;
import static redis_request.RedisRequestOuterClass.RequestType.PfAdd;
Expand Down Expand Up @@ -731,6 +733,48 @@ public void ttl_returns_success() {
assertEquals(ttl, response.get());
}

@SneakyThrows
@Test
public void expiretime_returns_success() {
// setup
String key = "testKey";
long value = 999L;
CompletableFuture<Long> testResponse = new CompletableFuture<>();
testResponse.complete(value);

// match on protobuf request
when(commandManager.<Long>submitNewCommand(eq(ExpireTime), eq(new String[] {key}), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Long> response = service.expiretime(key);

// verify
assertEquals(testResponse, response);
assertEquals(value, response.get());
}

@SneakyThrows
@Test
public void pexpiretime_returns_success() {
// setup
String key = "testKey";
long value = 999L;
CompletableFuture<Long> testResponse = new CompletableFuture<>();
testResponse.complete(value);

// match on protobuf request
when(commandManager.<Long>submitNewCommand(eq(PExpireTime), eq(new String[] {key}), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Long> response = service.pexpiretime(key);

// verify
assertEquals(testResponse, response);
assertEquals(value, response.get());
}

@SneakyThrows
@Test
public void invokeScript_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Exists;
import static redis_request.RedisRequestOuterClass.RequestType.Expire;
import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt;
import static redis_request.RedisRequestOuterClass.RequestType.ExpireTime;
import static redis_request.RedisRequestOuterClass.RequestType.FlushAll;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionLoad;
import static redis_request.RedisRequestOuterClass.RequestType.GeoAdd;
Expand Down Expand Up @@ -93,6 +94,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ObjectRefCount;
import static redis_request.RedisRequestOuterClass.RequestType.PExpire;
import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt;
import static redis_request.RedisRequestOuterClass.RequestType.PExpireTime;
import static redis_request.RedisRequestOuterClass.RequestType.PTTL;
import static redis_request.RedisRequestOuterClass.RequestType.Persist;
import static redis_request.RedisRequestOuterClass.RequestType.PfAdd;
Expand Down Expand Up @@ -403,6 +405,12 @@ public void transaction_builds_protobuf_request(BaseTransaction<?> transaction)
transaction.pttl("key");
results.add(Pair.of(PTTL, buildArgs("key")));

transaction.expiretime("key");
results.add(Pair.of(ExpireTime, buildArgs("key")));

transaction.pexpiretime("key");
results.add(Pair.of(PExpireTime, buildArgs("key")));

transaction.clientId();
results.add(Pair.of(ClientId, buildArgs()));

Expand Down
19 changes: 16 additions & 3 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -1391,7 +1391,7 @@ public void exists_multiple_keys(BaseClient client) {
@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
public void expire_pexpire_and_ttl_with_positive_timeout(BaseClient client) {
public void expire_pexpire_ttl_and_expiretime_with_positive_timeout(BaseClient client) {
String key = UUID.randomUUID().toString();
assertEquals(OK, client.set(key, "expire_timeout").get());
assertTrue(client.expire(key, 10L).get());
Expand All @@ -1411,6 +1411,8 @@ public void expire_pexpire_and_ttl_with_positive_timeout(BaseClient client) {
assertTrue(client.expire(key, 15L).get());
} else {
assertTrue(client.expire(key, 15L, ExpireOptions.HAS_EXISTING_EXPIRY).get());
assertTrue(client.expiretime(key).get() > Instant.now().getEpochSecond());
assertTrue(client.pexpiretime(key).get() > Instant.now().toEpochMilli());
}
assertTrue(client.ttl(key).get() <= 15L);
}
Expand Down Expand Up @@ -1454,11 +1456,18 @@ public void expireAt_pexpireAt_and_ttl_with_positive_timeout(BaseClient client)
@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
public void expire_pexpire_ttl_with_timestamp_in_the_past_or_negative_timeout(BaseClient client) {
public void expire_pexpire_ttl_and_expiretime_with_timestamp_in_the_past_or_negative_timeout(
BaseClient client) {
String key = UUID.randomUUID().toString();

assertEquals(OK, client.set(key, "expire_with_past_timestamp").get());
// no timeout set yet
assertEquals(-1L, client.ttl(key).get());
if (REDIS_VERSION.isGreaterThanOrEqualTo("7.0.0")) {
assertEquals(-1L, client.expiretime(key).get());
assertEquals(-1L, client.pexpiretime(key).get());
}

assertTrue(client.expire(key, -10L).get());
assertEquals(-2L, client.ttl(key).get());

Expand Down Expand Up @@ -1488,13 +1497,17 @@ public void expireAt_pexpireAt_ttl_with_timestamp_in_the_past_or_negative_timeou
@SneakyThrows
@ParameterizedTest(autoCloseArguments = false)
@MethodSource("getClients")
public void expire_pexpire_and_ttl_with_non_existing_key(BaseClient client) {
public void expire_pexpire_ttl_and_expiretime_with_non_existing_key(BaseClient client) {
String key = UUID.randomUUID().toString();

assertFalse(client.expire(key, 10L).get());
assertFalse(client.pexpire(key, 10000L).get());

assertEquals(-2L, client.ttl(key).get());
if (REDIS_VERSION.isGreaterThanOrEqualTo("7.0.0")) {
assertEquals(-2L, client.expiretime(key).get());
assertEquals(-2L, client.pexpiretime(key).get());
}
}

@SneakyThrows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ private static Object[] genericCommands(BaseTransaction<?> transaction) {
.expire(genericKey1, 42, ExpireOptions.HAS_NO_EXPIRY)
.expireAt(genericKey1, 500, ExpireOptions.HAS_EXISTING_EXPIRY)
.pexpire(genericKey1, 42, ExpireOptions.NEW_EXPIRY_GREATER_THAN_CURRENT)
.pexpireAt(genericKey1, 42, ExpireOptions.HAS_NO_EXPIRY);
.pexpireAt(genericKey1, 42, ExpireOptions.HAS_NO_EXPIRY)
.expiretime(genericKey1)
.pexpiretime(genericKey1);
}

var expectedResults =
Expand Down Expand Up @@ -150,6 +152,8 @@ private static Object[] genericCommands(BaseTransaction<?> transaction) {
true, // expireAt(genericKey1, 500, ExpireOptions.HAS_EXISTING_EXPIRY)
false, // pexpire(genericKey1, 42, ExpireOptions.NEW_EXPIRY_GREATER_THAN_CURRENT)
false, // pexpireAt(genericKey1, 42, ExpireOptions.HAS_NO_EXPIRY)
-2L, // expiretime(genericKey1)
-2L, // pexpiretime(genericKey1)
});
}
return expectedResults;
Expand Down

0 comments on commit 6b5079a

Please sign in to comment.