diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 8265c4feca..995a9126ed 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -171,6 +171,7 @@ enum RequestType { GeoPos = 128; BZPopMax = 129; ObjectFreq = 130; + Touch = 132; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index 6bb0149f56..94e5161a6f 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -139,6 +139,7 @@ pub enum RequestType { GeoPos = 128, BZPopMax = 129, ObjectFreq = 130, + Touch = 132, } fn get_two_word_command(first: &str, second: &str) -> Cmd { @@ -281,6 +282,7 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::LOLWUT => RequestType::LOLWUT, ProtobufRequestType::GeoPos => RequestType::GeoPos, ProtobufRequestType::BZPopMax => RequestType::BZPopMax, + ProtobufRequestType::Touch => RequestType::Touch, } } } @@ -419,6 +421,7 @@ impl RequestType { RequestType::LOLWUT => Some(cmd("LOLWUT")), RequestType::GeoPos => Some(cmd("GEOPOS")), RequestType::BZPopMax => Some(cmd("BZPOPMAX")), + RequestType::Touch => Some(cmd("TOUCH")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 3b148fc62b..76a7864a79 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -72,6 +72,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.SetString; import static redis_request.RedisRequestOuterClass.RequestType.Strlen; import static redis_request.RedisRequestOuterClass.RequestType.TTL; +import static redis_request.RedisRequestOuterClass.RequestType.Touch; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; @@ -1034,4 +1035,9 @@ public CompletableFuture pfmerge( String[] arguments = ArrayUtils.addFirst(sourceKeys, destination); return commandManager.submitNewCommand(PfMerge, arguments, this::handleStringResponse); } + + @Override + public CompletableFuture touch(@NonNull String[] keys) { + return commandManager.submitNewCommand(Touch, keys, this::handleLongResponse); + } } 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 0c5c64b62b..e0b562e727 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -445,4 +445,20 @@ CompletableFuture pexpireAt( * } */ CompletableFuture objectRefcount(String key); + + /** + * Updates the last access time of specified keys. + * + * @apiNote When in cluster mode, the command may route to multiple nodes when keys + * map to different hash slots. + * @see redis.io for details. + * @param keys The keys to update last access time. + * @return The number of keys that were updated. + * @example + *
{@code
+     * Long payload = client.touch(new String[] {"myKey1", "myKey2", "nonExistentKey"}).get();
+     * assert payload == 2L; // Last access time of 2 keys has been updated.
+     * }
+ */ + CompletableFuture touch(String[] keys); } 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 4db361320e..7989d3d975 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -87,6 +87,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Strlen; import static redis_request.RedisRequestOuterClass.RequestType.TTL; import static redis_request.RedisRequestOuterClass.RequestType.Time; +import static redis_request.RedisRequestOuterClass.RequestType.Touch; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; @@ -2424,6 +2425,19 @@ public T objectRefcount(@NonNull String key) { return getThis(); } + /** + * Updates the last access time of specified keys. + * + * @see redis.io for details. + * @param keys The keys to update last access time. + * @return Command Response - The number of keys that were updated. + */ + public T touch(@NonNull String[] keys) { + ArgsArray commandArgs = buildArgs(keys); + protobufTransaction.addCommands(buildCommand(Touch, 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 5e4b257e3a..2cba260f80 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -108,6 +108,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Strlen; import static redis_request.RedisRequestOuterClass.RequestType.TTL; import static redis_request.RedisRequestOuterClass.RequestType.Time; +import static redis_request.RedisRequestOuterClass.RequestType.Touch; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; @@ -3533,4 +3534,26 @@ public void objectRefcount_returns_success() { assertEquals(testResponse, response); assertEquals(refcount, payload); } + + @SneakyThrows + @Test + public void touch_returns_success() { + // setup + String[] keys = new String[] {"testKey1", "testKey2"}; + Long value = 2L; + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Touch), eq(keys), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.touch(keys); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } } 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 c967576c46..8d78823543 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -94,6 +94,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Strlen; import static redis_request.RedisRequestOuterClass.RequestType.TTL; import static redis_request.RedisRequestOuterClass.RequestType.Time; +import static redis_request.RedisRequestOuterClass.RequestType.Touch; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; @@ -547,6 +548,9 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), transaction.objectRefcount("key"); results.add(Pair.of(ObjectRefcount, buildArgs("key"))); + transaction.touch(new String[] {"key1", "key2"}); + results.add(Pair.of(Touch, buildArgs("key1", "key2"))); + 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 227be290cb..489d54ccfb 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -2842,4 +2842,20 @@ public void objectRefcount(BaseClient client) { assertEquals(OK, client.set(key, "").get()); assertTrue(client.objectRefcount(key).get() >= 0L); } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void touch(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String key3 = UUID.randomUUID().toString(); + String value = "{value}" + UUID.randomUUID(); + + assertEquals(OK, client.set(key1, value).get()); + assertEquals(OK, client.set(key2, value).get()); + + assertEquals(2, client.touch(new String[] {key1, key2}).get()); + assertEquals(0, client.touch(new String[] {key3}).get()); + } } diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 6b1c2bec54..e3f2fdfecc 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -56,6 +56,8 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.exists(new String[] {key1}); baseTransaction.persist(key1); + baseTransaction.touch(new String[] {key1}); + baseTransaction.del(new String[] {key1}); baseTransaction.get(key1); @@ -186,6 +188,7 @@ public static Object[] transactionTestResult() { new String[] {value1, value2}, 1L, Boolean.FALSE, // persist(key1) + 1L, // touch(new String[] {key1}) 1L, null, 1L,