From 4447db7f92e487b259baaf7d29d7b6418240d96f Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Sun, 21 Apr 2024 22:42:21 -0700 Subject: [PATCH] Implement OBJECT REFCOUNT 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 | 16 +++++++++++++ .../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, 114 insertions(+) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index d86eeb0445..c96dc7c06d 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; + ObjectRefcount = 126; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index edf7c27dbe..e6c739d70d 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, + ObjectRefcount = 126, } 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::ObjectRefcount => RequestType::ObjectRefcount, } } } @@ -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::ObjectRefcount => Some(get_two_word_command("OBJECT", "REFCOUNT")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index f9812dd4fb..cbe5b8e9ce 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.ObjectRefcount; 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 objectRefcount(@NonNull String key) { + return commandManager.submitNewCommand( + ObjectRefcount, 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..ea8030679b 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 reference count of the object stored at key. + * + * @see redis.io for details. + * @param key The key of the object to get the reference count of. + * @return If key exists, returns the reference count of the object stored at + * key as a Long. Otherwise, returns null. + * @example + *
{@code
+     * Long refcount = client.objectRefcount("my_hash").get();
+     * assert refcount == 2L;
+     *
+     * refcount = client.objectRefcount("non_existing_key").get();
+     * assert refcount == null;
+     * }
+ */ + CompletableFuture objectRefcount(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..4fd361aa5f 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.ObjectRefcount; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; @@ -2240,6 +2241,21 @@ public T objectEncoding(@NonNull String key) { return getThis(); } + /** + * Returns the reference count of the object stored at key. + * + * @see redis.io for details. + * @param key The key of the object to get the reference count of. + * @return Command response - If key exists, returns the reference count of the + * object stored at key as a Long. Otherwise, returns null + * . + */ + public T objectRefcount(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + protobufTransaction.addCommands(buildCommand(ObjectRefcount, 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..f576436cc7 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.ObjectRefcount; 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 objectRefcount_returns_success() { + // setup + String key = "testKey"; + Long refcount = 0L; + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(refcount); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ObjectRefcount), eq(new String[] {key}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.objectRefcount(key); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(refcount, 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..06cf6ead69 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.ObjectRefcount; 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.objectRefcount("key"); + results.add(Pair.of(ObjectRefcount, 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..4f75b4b691 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 objectRefcount_returns_null(BaseClient client) { + String nonExistingKey = UUID.randomUUID().toString(); + assertNull(client.objectRefcount(nonExistingKey).get()); + } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void objectRefcount(BaseClient client) { + String key = UUID.randomUUID().toString(); + assertEquals(OK, client.set(key, "").get()); + assertTrue(client.objectRefcount(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..13a8a0ab3c 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 objectRefcount() { + // String objectRefcountKey = "key"; + // ClusterTransaction transaction = new ClusterTransaction(); + // transaction.set(objectRefcountKey, ""); + // transaction.objectRefcount(objectRefcountKey); + // 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..54a03488a3 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 objectRefcount() { + String objectRefcountKey = "key"; + Transaction transaction = new Transaction(); + transaction.set(objectRefcountKey, ""); + transaction.objectRefcount(objectRefcountKey); + var response = client.exec(transaction).get(); + assertEquals(OK, response[0]); + assertTrue((long) response[1] >= 0L); + } }