From 2f466067aa35ed12fc1cd86f25065888516a12bd Mon Sep 17 00:00:00 2001 From: tjzhang-BQ <111323543+tjzhang-BQ@users.noreply.github.com> Date: Mon, 13 May 2024 16:32:54 -0700 Subject: [PATCH] JAVA: Add command GEODIST (#272) (#1402) * JAVA: Add command GEODIST (#272) * JAVA: Add command GEODIST * allowing for overloaded signature to pass command with no metric option * undo changes to examplesApp * removing geoadd from geodist example * converting comments to javadocs, removing explicit array creation & redundant test case --------- Co-authored-by: TJ Zhang * fixing comment * addressing review comments * adding clickable link for metric value in javadoc --------- Co-authored-by: TJ Zhang --- .../src/main/java/glide/api/BaseClient.java | 19 +++++++ .../GeospatialIndicesBaseCommands.java | 39 +++++++++++++ .../glide/api/models/BaseTransaction.java | 42 ++++++++++++++ .../models/commands/geospatial/GeoUnit.java | 25 +++++++++ .../test/java/glide/api/RedisClientTest.java | 55 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 7 +++ .../test/java/glide/SharedCommandTests.java | 40 ++++++++++++++ .../java/glide/TransactionTestUtilities.java | 7 ++- 8 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 java/client/src/main/java/glide/api/models/commands/geospatial/GeoUnit.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 86d4a371f4..354545daca 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -21,6 +21,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Expire; import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.GeoAdd; +import static redis_request.RedisRequestOuterClass.RequestType.GeoDist; import static redis_request.RedisRequestOuterClass.RequestType.GeoPos; import static redis_request.RedisRequestOuterClass.RequestType.Get; import static redis_request.RedisRequestOuterClass.RequestType.GetRange; @@ -135,6 +136,7 @@ import glide.api.models.commands.WeightAggregateOptions.KeysOrWeightedKeys; import glide.api.models.commands.ZAddOptions; import glide.api.models.commands.geospatial.GeoAddOptions; +import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; import glide.api.models.commands.stream.StreamAddOptions; import glide.api.models.commands.stream.StreamTrimOptions; @@ -1241,6 +1243,23 @@ public CompletableFuture geopos(@NonNull String key, @NonNull String response -> castArrayofArrays(handleArrayResponse(response), Double.class)); } + @Override + public CompletableFuture geodist( + @NonNull String key, + @NonNull String member1, + @NonNull String member2, + @NonNull GeoUnit geoUnit) { + String[] arguments = new String[] {key, member1, member2, geoUnit.getRedisApi()}; + return commandManager.submitNewCommand(GeoDist, arguments, this::handleDoubleOrNullResponse); + } + + @Override + public CompletableFuture geodist( + @NonNull String key, @NonNull String member1, @NonNull String member2) { + String[] arguments = new String[] {key, member1, member2}; + return commandManager.submitNewCommand(GeoDist, arguments, this::handleDoubleOrNullResponse); + } + @Override public CompletableFuture bitcount(@NonNull String key) { return commandManager.submitNewCommand(Bitcount, new String[] {key}, this::handleLongResponse); diff --git a/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java b/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java index 4ed3bb295a..59f689d613 100644 --- a/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GeospatialIndicesBaseCommands.java @@ -2,6 +2,7 @@ package glide.api.commands; import glide.api.models.commands.geospatial.GeoAddOptions; +import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -78,4 +79,42 @@ CompletableFuture geoadd( * } */ CompletableFuture geopos(String key, String[] members); + + /** + * Returns the distance between member1 and member2 saved in the + * geospatial index stored at key. + * + * @see valkey.io for more details. + * @param key The key of the sorted set. + * @param member1 The name of the first member. + * @param member2 The name of the second member. + * @param geoUnit The unit of distance measurement - see {@link GeoUnit}. + * @return The distance between member1 and member2. If one or both + * members do not exist, or if the key does not exist, returns null. + * @example + *
{@code
+     * Double result = client.geodist("mySortedSet", "Palermo", "Catania", GeoUnit.KILOMETERS).get();
+     * System.out.println(result);
+     * }
+ */ + CompletableFuture geodist(String key, String member1, String member2, GeoUnit geoUnit); + + /** + * Returns the distance between member1 and member2 saved in the + * geospatial index stored at key. + * + * @see valkey.io for more details. + * @param key The key of the sorted set. + * @param member1 The name of the first member. + * @param member2 The name of the second member. + * @return The distance between member1 and member2. If one or both + * members do not exist, or if the key does not exist, returns null. The default + * unit is {@see GeoUnit#METERS}. + * @example + *
{@code
+     * Double result = client.geodist("mySortedSet", "Palermo", "Catania").get();
+     * System.out.println(result);
+     * }
+ */ + CompletableFuture geodist(String key, String member1, String member2); } 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 9e40d86ef5..1a5b4fa139 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -32,6 +32,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.FlushAll; import static redis_request.RedisRequestOuterClass.RequestType.GeoAdd; +import static redis_request.RedisRequestOuterClass.RequestType.GeoDist; import static redis_request.RedisRequestOuterClass.RequestType.GeoPos; import static redis_request.RedisRequestOuterClass.RequestType.Get; import static redis_request.RedisRequestOuterClass.RequestType.GetRange; @@ -154,6 +155,7 @@ import glide.api.models.commands.WeightAggregateOptions.WeightedKeys; import glide.api.models.commands.ZAddOptions; import glide.api.models.commands.geospatial.GeoAddOptions; +import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; import glide.api.models.commands.stream.StreamAddOptions; import glide.api.models.commands.stream.StreamAddOptions.StreamAddOptionsBuilder; @@ -3037,6 +3039,46 @@ public T geopos(@NonNull String key, @NonNull String[] members) { return getThis(); } + /** + * Returns the distance between member1 and member2 saved in the + * geospatial index stored at key. + * + * @see valkey.io for more details. + * @param key The key of the sorted set. + * @param member1 The name of the first member. + * @param member2 The name of the second member. + * @param geoUnit The unit of distance measurement - see {@link GeoUnit}. + * @return Command Response - The distance between member1 and member2. + * If one or both members do not exist or if the key does not exist returns null. + */ + public T geodist( + @NonNull String key, + @NonNull String member1, + @NonNull String member2, + @NonNull GeoUnit geoUnit) { + ArgsArray commandArgs = buildArgs(key, member1, member2, geoUnit.getRedisApi()); + protobufTransaction.addCommands(buildCommand(GeoDist, commandArgs)); + return getThis(); + } + + /** + * Returns the distance between member1 and member2 saved in the + * geospatial index stored at key. + * + * @see valkey.io for more details. + * @param key The key of the sorted set. + * @param member1 The name of the first member. + * @param member2 The name of the second member. + * @return Command Response - The distance between member1 and member2. + * If one or both members do not exist or if the key does not exist returns null. + * The default unit is {@see GeoUnit#METERS}. + */ + public T geodist(@NonNull String key, @NonNull String member1, @NonNull String member2) { + ArgsArray commandArgs = buildArgs(key, member1, member2); + protobufTransaction.addCommands(buildCommand(GeoDist, 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/main/java/glide/api/models/commands/geospatial/GeoUnit.java b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoUnit.java new file mode 100644 index 0000000000..c5120e50b8 --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/geospatial/GeoUnit.java @@ -0,0 +1,25 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands.geospatial; + +import glide.api.commands.GeospatialIndicesBaseCommands; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Enumeration representing distance units options for the {@link + * GeospatialIndicesBaseCommands#geodist(String, String, String, GeoUnit)} command. + */ +@RequiredArgsConstructor +@Getter +public enum GeoUnit { + /** Represents distance in meters. */ + METERS("m"), + /** Represents distance in kilometers. */ + KILOMETERS("km"), + /** Represents distance in miles. */ + MILES("mi"), + /** Represents distance in feet. */ + FEET("ft"); + + private final String redisApi; +} diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 8064810ba5..1894210f89 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -53,6 +53,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.FlushAll; import static redis_request.RedisRequestOuterClass.RequestType.GeoAdd; +import static redis_request.RedisRequestOuterClass.RequestType.GeoDist; import static redis_request.RedisRequestOuterClass.RequestType.GeoPos; import static redis_request.RedisRequestOuterClass.RequestType.Get; import static redis_request.RedisRequestOuterClass.RequestType.GetRange; @@ -169,6 +170,7 @@ import glide.api.models.commands.WeightAggregateOptions.WeightedKeys; import glide.api.models.commands.ZAddOptions; import glide.api.models.commands.geospatial.GeoAddOptions; +import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; import glide.api.models.commands.stream.StreamAddOptions; import glide.api.models.commands.stream.StreamTrimOptions; @@ -4186,6 +4188,59 @@ public void geopos_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void geodist_returns_success() { + // setup + String key = "testKey"; + String member1 = "Catania"; + String member2 = "Palermo"; + String[] arguments = new String[] {key, member1, member2}; + Double value = 166274.1516; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(GeoDist), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.geodist(key, member1, member2); + Double payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void geodist_with_metrics_returns_success() { + // setup + String key = "testKey"; + String member1 = "Catania"; + String member2 = "Palermo"; + GeoUnit geoUnit = GeoUnit.KILOMETERS; + String[] arguments = new String[] {key, member1, member2, GeoUnit.KILOMETERS.getRedisApi()}; + Double value = 166.2742; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(GeoDist), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.geodist(key, member1, member2, geoUnit); + Double payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void bitcount_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 18348d13a4..29b4091000 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -43,6 +43,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.FlushAll; import static redis_request.RedisRequestOuterClass.RequestType.GeoAdd; +import static redis_request.RedisRequestOuterClass.RequestType.GeoDist; import static redis_request.RedisRequestOuterClass.RequestType.GeoPos; import static redis_request.RedisRequestOuterClass.RequestType.Get; import static redis_request.RedisRequestOuterClass.RequestType.GetRange; @@ -153,6 +154,7 @@ import glide.api.models.commands.WeightAggregateOptions.WeightedKeys; import glide.api.models.commands.ZAddOptions; import glide.api.models.commands.geospatial.GeoAddOptions; +import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; import glide.api.models.commands.stream.StreamAddOptions; import glide.api.models.commands.stream.StreamTrimOptions.MinId; @@ -714,6 +716,11 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), transaction.geopos("key", new String[] {"Place"}); results.add(Pair.of(GeoPos, buildArgs("key", "Place"))); + transaction.geodist("key", "Place", "Place2"); + results.add(Pair.of(GeoDist, buildArgs("key", "Place", "Place2"))); + transaction.geodist("key", "Place", "Place2", GeoUnit.KILOMETERS); + results.add(Pair.of(GeoDist, buildArgs("key", "Place", "Place2", "km"))); + transaction.bitcount("key"); results.add(Pair.of(Bitcount, buildArgs("key"))); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index b883f4a873..b30d5e3455 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -48,6 +48,7 @@ import glide.api.models.commands.WeightAggregateOptions.WeightedKeys; import glide.api.models.commands.ZAddOptions; import glide.api.models.commands.geospatial.GeoAddOptions; +import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; import glide.api.models.commands.stream.StreamAddOptions; import glide.api.models.commands.stream.StreamTrimOptions.MaxLen; @@ -3569,6 +3570,45 @@ public void geopos(BaseClient client) { assertTrue(executionException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void geodist(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String member1 = "Palermo"; + String member2 = "Catania"; + String member3 = "NonExisting"; + GeoUnit geoUnitKM = GeoUnit.KILOMETERS; + Double expected = 166274.1516; + Double expectedKM = 166.2742; + Double delta = 1e-9; + + // adding locations + Map membersToCoordinates = new HashMap<>(); + membersToCoordinates.put("Palermo", new GeospatialData(13.361389, 38.115556)); + membersToCoordinates.put("Catania", new GeospatialData(15.087269, 37.502669)); + assertEquals(2, client.geoadd(key1, membersToCoordinates).get()); + + // assert correct result with default metric + Double actual = client.geodist(key1, member1, member2).get(); + assertEquals(expected, actual, delta); + + // assert correct result with manual metric specification kilometers + Double actualKM = client.geodist(key1, member1, member2, geoUnitKM).get(); + assertEquals(expectedKM, actualKM, delta); + + // assert null result when member index is missing + Double actualMissing = client.geodist(key1, member1, member3).get(); + assertNull(actualMissing); + + // key exists but holds a non-ZSET value + assertEquals(OK, client.set(key2, "geodist").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.geodist(key2, member1, member2).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + @SneakyThrows @ParameterizedTest(autoCloseArguments = false) @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 385ec2584f..ae1f4a5012 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -20,6 +20,7 @@ import glide.api.models.commands.SetOptions; import glide.api.models.commands.WeightAggregateOptions.Aggregate; import glide.api.models.commands.WeightAggregateOptions.KeyArray; +import glide.api.models.commands.geospatial.GeoUnit; import glide.api.models.commands.geospatial.GeospatialData; import glide.api.models.commands.stream.StreamAddOptions; import glide.api.models.commands.stream.StreamTrimOptions.MinId; @@ -478,13 +479,17 @@ private static Object[] geospatialCommands(BaseTransaction transaction) { "Catania", new GeospatialData(15.087269, 37.502669))) .geopos(geoKey1, new String[] {"Palermo", "Catania"}); + transaction.geodist(geoKey1, "Palermo", "Catania"); + transaction.geodist(geoKey1, "Palermo", "Catania", GeoUnit.KILOMETERS); return new Object[] { 2L, // geoadd(geoKey1, Map.of("Palermo", ..., "Catania", ...)) new Double[][] { {13.36138933897018433, 38.11555639549629859}, {15.08726745843887329, 37.50266842333162032}, - }, // geopos(new String[]{"Palermo", "Catania"}) + }, // geopos(geoKey1, new String[]{"Palermo", "Catania"}) + 166274.1516, // geodist(geoKey1, "Palermo", "Catania") + 166.2742, // geodist(geoKey1, "Palermo", "Catania", GeoUnit.KILOMETERS) }; }