diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 48bf3a50b4..cf5edc20e4 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -34,6 +34,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; +import static redis_request.RedisRequestOuterClass.RequestType.Persist; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; @@ -586,4 +587,10 @@ public CompletableFuture zrem(@NonNull String key, @NonNull String[] membe public CompletableFuture zcard(@NonNull String key) { return commandManager.submitNewCommand(Zcard, new String[] {key}, this::handleLongResponse); } + + @Override + public CompletableFuture persist(@NonNull String key) { + return commandManager.submitNewCommand( + Persist, new String[] {key}, this::handleBooleanResponse); + } } 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 18d1f9dd36..12c8a2712f 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -269,4 +269,21 @@ CompletableFuture pexpireAt( * } */ CompletableFuture ttl(String key); + + /** + * Remove the existing timeout on key, turning the key from volatile (a + * key with an expire set) to persistent (a key that will never expire + * as no timeout is associated). + * + * @see redis.io for details. + * @param key The key to remove the existing timeout on. + * @return false if key does not exist or does not have an associated + * timeout, true if the timeout has been removed. + * @example + *
{@code
+     * Boolean timeoutRemoved = client.persist("my_key").get();
+     * assert timeoutRemoved; // Indicates that the timeout associated with the key "my_key" was successfully removed.
+     * }
+ */ + CompletableFuture persist(String key); } 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 d0c1d4814c..237c0c5458 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -40,6 +40,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; +import static redis_request.RedisRequestOuterClass.RequestType.Persist; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -1241,6 +1242,22 @@ public T zcard(@NonNull String key) { return getThis(); } + /** + * Remove the existing timeout on key, turning the key from volatile (a + * key with an expire set) to persistent (a key that will never expire + * as no timeout is associated). + * + * @see redis.io for details. + * @param key The key to remove the existing timeout on. + * @return Command Response - false if key does not exist or does not + * have an associated timeout, true if the timeout has been removed. + */ + public T persist(@NonNull String key) { + ArgsArray commandArgs = buildArgs(new String[] {key}); + protobufTransaction.addCommands(buildCommand(Persist, commandArgs)); + return getThis(); + } + /** Build protobuf {@link Command} object for given command and arguments. */ protected Command buildCommand(RequestType requestType) { return buildCommand(requestType, buildArgs()); diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 2aee44867d..f83953db0c 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -51,6 +51,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; +import static redis_request.RedisRequestOuterClass.RequestType.Persist; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -551,6 +552,28 @@ public void ttl_returns_success() { assertEquals(ttl, response.get()); } + @SneakyThrows + @Test + public void persist_returns_success() { + // setup + String key = "testKey"; + Boolean isTimeoutRemoved = true; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(isTimeoutRemoved); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Persist), eq(new String[] {key}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.persist(key); + + // verify + assertEquals(testResponse, response); + assertEquals(isTimeoutRemoved, response.get()); + } + @SneakyThrows @Test public void info_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 7d1f98430f..961148704a 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -38,6 +38,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; +import static redis_request.RedisRequestOuterClass.RequestType.Persist; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -372,6 +373,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.zcard("key"); results.add(Pair.of(Zcard, ArgsArray.newBuilder().addArgs("key").build())); + transaction.persist("key"); + results.add(Pair.of(Persist, ArgsArray.newBuilder().addArgs("key").build())); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index ab565d1f1e..528cfe8970 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -861,6 +861,19 @@ public void expireAt_pexpireAt_and_ttl_with_non_existing_key(BaseClient client) assertEquals(-2L, client.ttl(key).get()); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void persist_on_existing_key(BaseClient client) { + String key = UUID.randomUUID().toString(); + + assertEquals(OK, client.set(key, "foo").get()); + assertFalse(client.persist(key).get()); + + assertTrue(client.expire(key, 10).get()); + assertTrue(client.persist(key).get()); + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 4c74b4b24d..3d757ec1d6 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -34,6 +34,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.customCommand(new String[] {"MGET", key1, key2}); baseTransaction.exists(new String[] {key1}); + baseTransaction.persist(key1); baseTransaction.del(new String[] {key1}); baseTransaction.get(key1); @@ -101,6 +102,7 @@ public static Object[] transactionTestResult() { null, new String[] {value1, value2}, 1L, + Boolean.FALSE, 1L, null, 1L,