From ebe793ee5d10914e55c2dbee11356a7d200e942d Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Sun, 21 Apr 2024 21:59:31 -0700 Subject: [PATCH] Implement OBJECT IDLETIME command --- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/request_type.rs | 3 +++ .../src/main/java/glide/api/BaseClient.java | 7 ++++++ .../api/commands/GenericBaseCommands.java | 18 +++++++++++++++ .../glide/api/models/BaseTransaction.java | 15 ++++++++++++ .../test/java/glide/api/RedisClientTest.java | 23 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 4 ++++ .../test/java/glide/SharedCommandTests.java | 17 ++++++++++++++ .../cluster/ClusterTransactionTests.java | 13 +++++++++++ .../glide/standalone/TransactionTests.java | 12 ++++++++++ 10 files changed, 113 insertions(+) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index d86eeb0445..ce3b132f04 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -163,6 +163,7 @@ enum RequestType { GeoAdd = 121; GeoHash = 122; ObjectEncoding = 123; + ObjectIdletime = 125; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index edf7c27dbe..e59d25ce4c 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -131,6 +131,7 @@ pub enum RequestType { GeoAdd = 121, GeoHash = 122, ObjectEncoding = 123, + ObjectIdletime = 125, } fn get_two_word_command(first: &str, second: &str) -> Cmd { @@ -265,6 +266,7 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::GeoAdd => RequestType::GeoAdd, ProtobufRequestType::GeoHash => RequestType::GeoHash, ProtobufRequestType::ObjectEncoding => RequestType::ObjectEncoding, + ProtobufRequestType::ObjectIdletime => RequestType::ObjectIdletime, } } } @@ -395,6 +397,7 @@ impl RequestType { RequestType::GeoAdd => Some(cmd("GEOADD")), RequestType::GeoHash => Some(cmd("GEOHASH")), RequestType::ObjectEncoding => Some(get_two_word_command("OBJECT", "ENCODING")), + RequestType::ObjectIdletime => Some(get_two_word_command("OBJECT", "IDLETIME")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index f9812dd4fb..b981e9821b 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -42,6 +42,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.ObjectEncoding; +import static redis_request.RedisRequestOuterClass.RequestType.ObjectIdletime; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; @@ -341,6 +342,12 @@ public CompletableFuture objectEncoding(@NonNull String key) { ObjectEncoding, new String[] {key}, this::handleStringOrNullResponse); } + @Override + public CompletableFuture objectIdletime(@NonNull String key) { + return commandManager.submitNewCommand( + ObjectIdletime, new String[] {key}, this::handleLongOrNullResponse); + } + @Override public CompletableFuture incr(@NonNull String key) { return commandManager.submitNewCommand(Incr, new String[] {key}, 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 ede7fede20..0fab0eef5c 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -389,4 +389,22 @@ CompletableFuture pexpireAt( * } */ CompletableFuture objectEncoding(String key); + + /** + * Returns the time in seconds since the last access to the value stored at key. + * + * @see redis.io for details. + * @param key The key of the object to get the idle time of. + * @return If key exists, returns the idle time in seconds of the object at key + * as a Long. Otherwise, returns null. + * @example + *
{@code
+     * Long idletime = client.objectIdletime("my_hash").get();
+     * assert idletime == 2L;
+     *
+     * idletime = client.objectIdletime("non_existing_key").get();
+     * assert idletime == null;
+     * }
+ */ + CompletableFuture objectIdletime(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 16be2fd746..8cc3fd7964 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -53,6 +53,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.ObjectEncoding; +import static redis_request.RedisRequestOuterClass.RequestType.ObjectIdletime; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; @@ -2240,6 +2241,20 @@ public T objectEncoding(@NonNull String key) { return getThis(); } + /** + * Returns the time in seconds since the last access to the value stored at key. + * + * @see redis.io for details. + * @param key The key of the object to get the idle time of. + * @return Command response - If key exists, returns the idle time in seconds of the + * object at key as a Long. Otherwise, returns null. + */ + public T objectIdletime(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + protobufTransaction.addCommands(buildCommand(ObjectIdletime, 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 46ad50f281..4ef87bddf0 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -73,6 +73,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.ObjectEncoding; +import static redis_request.RedisRequestOuterClass.RequestType.ObjectIdletime; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; @@ -3282,4 +3283,26 @@ public void objectEncoding_returns_success() { assertEquals(testResponse, response); assertEquals(encoding, payload); } + + @SneakyThrows + @Test + public void objectIdletime_returns_success() { + // setup + String key = "testKey"; + Long idletime = 0L; + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(idletime); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ObjectIdletime), eq(new String[] {key}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.objectIdletime(key); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(idletime, 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 63adc075b8..de2fd907c6 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -58,6 +58,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.ObjectEncoding; +import static redis_request.RedisRequestOuterClass.RequestType.ObjectIdletime; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; @@ -512,6 +513,9 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), transaction.objectEncoding("key"); results.add(Pair.of(ObjectEncoding, buildArgs("key"))); + transaction.objectIdletime("key"); + results.add(Pair.of(ObjectIdletime, buildArgs("key"))); + 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 9b1a36ecc0..d54b60a5ee 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -2715,4 +2715,21 @@ public void objectEncoding_returns_stream(BaseClient client) { assertNotNull(client.xadd(streamKey, Map.of("field", "value"))); assertEquals("stream", client.objectEncoding(streamKey).get()); } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void objectIdletime_returns_null(BaseClient client) { + String nonExistingKey = UUID.randomUUID().toString(); + assertNull(client.objectIdletime(nonExistingKey).get()); + } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void objectIdletime(BaseClient client) { + String key = UUID.randomUUID().toString(); + assertEquals(OK, client.set(key, "").get()); + assertTrue(client.objectIdletime(key).get() >= 0L); + } } diff --git a/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java b/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java index 498893466f..c75e28146b 100644 --- a/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java +++ b/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java @@ -92,4 +92,17 @@ public void lastsave() { var response = clusterClient.exec(new ClusterTransaction().lastsave()).get(); assertTrue(Instant.ofEpochSecond((long) response[0]).isAfter(yesterday)); } + + // TODO: Enable when https://github.com/amazon-contributing/redis-rs/pull/138 is merged. + // @Test + // @SneakyThrows + // public void objectIdletime() { + // String objectIdletimeKey = "key"; + // ClusterTransaction transaction = new ClusterTransaction(); + // transaction.set(objectIdletimeKey, ""); + // transaction.objectIdletime(objectIdletimeKey); + // var response = clusterClient.exec(transaction).get(); + // assertEquals(OK, response[0]); + // assertTrue((long) response[1] >= 0L); + // } } diff --git a/java/integTest/src/test/java/glide/standalone/TransactionTests.java b/java/integTest/src/test/java/glide/standalone/TransactionTests.java index 532adb7703..4e650539bb 100644 --- a/java/integTest/src/test/java/glide/standalone/TransactionTests.java +++ b/java/integTest/src/test/java/glide/standalone/TransactionTests.java @@ -121,4 +121,16 @@ public void lastsave() { var response = client.exec(new Transaction().lastsave()).get(); assertTrue(Instant.ofEpochSecond((long) response[0]).isAfter(yesterday)); } + + @Test + @SneakyThrows + public void objectIdletime() { + String objectIdletimeKey = "key"; + Transaction transaction = new Transaction(); + transaction.set(objectIdletimeKey, ""); + transaction.objectIdletime(objectIdletimeKey); + var response = client.exec(transaction).get(); + assertEquals(OK, response[0]); + assertTrue((long) response[1] >= 0L); + } }