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 EXPIRETIME and PEXPIRETIME commands. #1462

Merged
merged 7 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from all 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: 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>
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
* 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>
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
* 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
Loading