From 6b5079ae44f7cea2c470f7e337b40d6d2a4a8dfe Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Fri, 24 May 2024 17:21:16 -0700 Subject: [PATCH] Java: Add `EXPIRETIME` and `PEXPIRETIME` commands. (#1462) * Java: Add `EXPIRETIME` and `PEXPIRETIME` commands. (#300) * Add `EXPIRETIME` and `PEXPIRETIME` commands. Signed-off-by: Yury-Fridlyand Co-authored-by: Guian Gumpac * Update expiretime docs Signed-off-by: Andrew Carbonetto * Update docs for expiretime Signed-off-by: Andrew Carbonetto * Apply suggestions from code review --------- Signed-off-by: Yury-Fridlyand Signed-off-by: Andrew Carbonetto Co-authored-by: Guian Gumpac Co-authored-by: Andrew Carbonetto Co-authored-by: Aaron <69273634+aaron-congo@users.noreply.github.com> --- glide-core/src/protobuf/redis_request.proto | 2 + glide-core/src/request_type.rs | 6 +++ .../src/main/java/glide/api/BaseClient.java | 14 ++++++ .../api/commands/GenericBaseCommands.java | 43 ++++++++++++++++-- .../glide/api/models/BaseTransaction.java | 36 +++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 44 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 8 ++++ .../test/java/glide/SharedCommandTests.java | 19 ++++++-- .../java/glide/TransactionTestUtilities.java | 6 ++- 9 files changed, 170 insertions(+), 8 deletions(-) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index e81303a644..2cf67e4f9d 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -191,6 +191,8 @@ enum RequestType { HStrlen = 149; FunctionLoad = 150; LMPop = 155; + ExpireTime = 156; + PExpireTime = 157; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index 36156c202a..d58ab19e38 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -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 { @@ -325,6 +327,8 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::BitPos => RequestType::BitPos, ProtobufRequestType::BitOp => RequestType::BitOp, ProtobufRequestType::HStrlen => RequestType::HStrlen, + ProtobufRequestType::ExpireTime => RequestType::ExpireTime, + ProtobufRequestType::PExpireTime => RequestType::PExpireTime, } } } @@ -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")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index bfc97aa6d2..e1f0cf94fe 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -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; @@ -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; @@ -802,6 +804,18 @@ public CompletableFuture ttl(@NonNull String key) { return commandManager.submitNewCommand(TTL, new String[] {key}, this::handleLongResponse); } + @Override + public CompletableFuture expiretime(@NonNull String key) { + return commandManager.submitNewCommand( + ExpireTime, new String[] {key}, this::handleLongResponse); + } + + @Override + public CompletableFuture pexpiretime(@NonNull String key) { + return commandManager.submitNewCommand( + PExpireTime, new String[] {key}, this::handleLongResponse); + } + @Override public CompletableFuture invokeScript(@NonNull Script script) { return commandManager.submitScript( diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index 7469627b61..c052ee4a45 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -266,7 +266,7 @@ CompletableFuture pexpireAt( * @see redis.io for details. * @param key The key to return its timeout. * @return TTL in seconds, -2 if key does not exist, or -1 - * if key exists but has no associated expire. + * if key exists but has no associated expiration. * @example *
{@code
      * Long timeRemaining = client.ttl("my_key").get();
@@ -278,8 +278,43 @@ CompletableFuture pexpireAt(
      */
     CompletableFuture 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 key
+     * will expire, in seconds.
+ * To get the expiration with millisecond precision, use {@link #pexpiretime(String)}. + * + * @since Redis 7.0 and above. + * @see redis.io for details. + * @param key The key to determine the expiration value of. + * @return The expiration Unix timestamp in seconds. -2 if key does not + * exist, or -1 if key exists but has no associated expiration. + * @example + *
{@code
+     * Long expiration = client.expiretime("my_key").get();
+     * System.out.printf("The key expires at %d epoch time", expiration);
+     * }
+ */ + CompletableFuture expiretime(String key); + + /** + * Returns the absolute Unix timestamp (since January 1, 1970) at which the given key + * will expire, in milliseconds. + * + * @since Redis 7.0 and above. + * @see redis.io for details. + * @param key The key to determine the expiration value of. + * @return The expiration Unix timestamp in milliseconds. -2 if key does + * not exist, or -1 if key exists but has no associated expiration. + * @example + *
{@code
+     * Long expiration = client.pexpiretime("my_key").get();
+     * System.out.printf("The key expires at %d epoch time (ms)", expiration);
+     * }
+ */ + CompletableFuture pexpiretime(String key); + + // TODO move invokeScript to ScriptingAndFunctionsBaseCommands + // TODO add note to invokeScript about routing on cluster client /** * Invokes a Lua script.
* This method simplifies the process of invoking scripts on a Redis server by using an object @@ -365,7 +400,7 @@ CompletableFuture pexpireAt( /** * Returns the string representation of the type of the value stored at key. * - * @see redis.io for details. * @param key The key to check its data type. * @return If the key exists, the type of the stored value is returned. Otherwise, a * "none" string is returned. diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 86a70fc2f1..86d918ed9a 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -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; @@ -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; @@ -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 key + * will expire, in seconds.
+ * To get the expiration with millisecond precision, use {@link #pexpiretime(String)}. + * + * @since Redis 7.0 and above. + * @see redis.io for details. + * @param key The key to determine the expiration value of. + * @return Command response - The expiration Unix timestamp in seconds, -2 if + * key does not exist, or -1 if key 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 key + * will expire, in milliseconds. + * + * @since Redis 7.0 and above. + * @see redis.io for details. + * @param key The key to determine the expiration value of. + * @return Command response - The expiration Unix timestamp in milliseconds, -2 if + * key + * does not exist, or -1 if key 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. * diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index b6dff5a6bb..530202e2df 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -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; @@ -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; @@ -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 testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ExpireTime), eq(new String[] {key}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture 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 testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PExpireTime), eq(new String[] {key}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pexpiretime(key); + + // verify + assertEquals(testResponse, response); + assertEquals(value, response.get()); + } + @SneakyThrows @Test public void invokeScript_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index adb50af73b..68a6d9409d 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -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; @@ -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; @@ -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())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index a5a101e08c..11b770f0dc 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -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()); @@ -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); } @@ -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()); @@ -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 diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index c6ba5f7f15..be63d1b106 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -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 = @@ -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;