From 7ca26232f4e6965e89b45c6c83db01c45aa5f91a Mon Sep 17 00:00:00 2001 From: tjzhang-BQ <111323543+tjzhang-BQ@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:19:37 -0700 Subject: [PATCH] Node: Add command GEODIST (#1988) * Node: Add command GEODIST --------- Signed-off-by: TJ Zhang Co-authored-by: TJ Zhang --- CHANGELOG.md | 1 + node/npm/glide/index.ts | 2 ++ node/src/BaseClient.ts | 31 ++++++++++++++++++++ node/src/Commands.ts | 35 +++++++++++++++++++++++ node/src/Transaction.ts | 24 ++++++++++++++++ node/tests/SharedTests.ts | 56 +++++++++++++++++++++++++++++++++++++ node/tests/TestUtilities.ts | 5 ++++ 7 files changed, 154 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb5d755dc6..22c596d983 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ * Node: Added DBSize command ([#1932](https://github.com/valkey-io/valkey-glide/pull/1932)) * Node: Added GeoAdd command ([#1980](https://github.com/valkey-io/valkey-glide/pull/1980)) * Node: Added ZRevRank command ([#1977](https://github.com/valkey-io/valkey-glide/pull/1977)) +* Node: Added GeoDist command ([#1988](https://github.com/valkey-io/valkey-glide/pull/1988)) #### Breaking Changes * Node: Update XREAD to return a Map of Map ([#1494](https://github.com/valkey-io/valkey-glide/pull/1494)) diff --git a/node/npm/glide/index.ts b/node/npm/glide/index.ts index dce8b31649..d4b45fe370 100644 --- a/node/npm/glide/index.ts +++ b/node/npm/glide/index.ts @@ -93,6 +93,7 @@ function initialize() { LPosOptions, ExpireOptions, FlushMode, + GeoUnit, InfoOptions, InsertPosition, SetOptions, @@ -146,6 +147,7 @@ function initialize() { LPosOptions, ExpireOptions, FlushMode, + GeoUnit, InfoOptions, InsertPosition, SetOptions, diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 390c5ca22e..54ff383b2f 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -35,6 +35,7 @@ import { createExpire, createExpireAt, createGeoAdd, + createGeoDist, createGeoPos, createGet, createGetBit, @@ -127,6 +128,7 @@ import { createZRevRank, createZRevRankWithScore, createZScore, + GeoUnit, } from "./Commands"; import { BitOffsetOptions } from "./commands/BitOffsetOptions"; import { GeoAddOptions } from "./commands/geospatial/GeoAddOptions"; @@ -3474,6 +3476,35 @@ export class BaseClient { return this.createWritePromise(createZMPop(key, modifier, count)); } + /** + * Returns the distance between `member1` and `member2` saved in the geospatial index stored at `key`. + * + * See https://valkey.io/commands/geodist/ 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}. If not specified, the default unit is {@link GeoUnit.METERS}. + * @returns The distance between `member1` and `member2`. Returns `null`, if one or both members do not exist, + * or if the key does not exist. + * + * @example + * ```typescript + * const result = await client.geodist("mySortedSet", "Place1", "Place2", GeoUnit.KILOMETERS); + * console.log(num); // Output: the distance between Place1 and Place2. + * ``` + */ + public geodist( + key: string, + member1: string, + member2: string, + geoUnit?: GeoUnit, + ): Promise { + return this.createWritePromise( + createGeoDist(key, member1, member2, geoUnit), + ); + } + /** * @internal */ diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 3eeb042ad7..72546bab0a 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -1857,6 +1857,23 @@ export function createGeoAdd( return createCommand(RequestType.GeoAdd, args); } +/** + * Enumeration representing distance units options for the {@link geodist} command. + */ +export 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", +} + /** * @internal */ @@ -1867,6 +1884,24 @@ export function createGeoPos( return createCommand(RequestType.GeoPos, [key].concat(members)); } +/** + * @internal + */ +export function createGeoDist( + key: string, + member1: string, + member2: string, + geoUnit?: GeoUnit, +): command_request.Command { + const args: string[] = [key, member1, member2]; + + if (geoUnit) { + args.push(geoUnit); + } + + return createCommand(RequestType.GeoDist, args); +} + /** * @internal */ diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index c04bc96817..30f42e49ee 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -43,6 +43,7 @@ import { createFunctionFlush, createFunctionLoad, createGeoAdd, + createGeoDist, createGeoPos, createGet, createGetBit, @@ -140,6 +141,7 @@ import { createZRevRank, createZRevRankWithScore, createZScore, + GeoUnit, } from "./Commands"; import { command_request } from "./ProtobufMessage"; import { BitOffsetOptions } from "./commands/BitOffsetOptions"; @@ -2033,6 +2035,28 @@ export class BaseTransaction> { public zmpop(keys: string[], modifier: ScoreFilter, count?: number): T { return this.addAndReturn(createZMPop(keys, modifier, count)); } + + /** + * Returns the distance between `member1` and `member2` saved in the geospatial index stored at `key`. + * + * See https://valkey.io/commands/geodist/ 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}. If not specified, the default unit is {@link GeoUnit.METERS}. + * + * Command Response - The distance between `member1` and `member2`. Returns `null`, if one or both members do not exist, + * or if the key does not exist. + */ + public geodist( + key: string, + member1: string, + member2: string, + geoUnit?: GeoUnit, + ): T { + return this.addAndReturn(createGeoDist(key, member1, member2, geoUnit)); + } } /** diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index a60c789d6c..f6f4c6ad9e 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -17,6 +17,7 @@ import { ScoreFilter, Script, parseInfoResponse, + GeoUnit, } from "../"; import { Client, @@ -4630,6 +4631,61 @@ export function runBaseTests(config: { }, config.timeout, ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `geodist test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const member1 = "Palermo"; + const member2 = "Catania"; + const nonExistingMember = "NonExisting"; + const expected = 166274.1516; + const expectedKM = 166.2742; + const delta = 1e-9; + + // adding the geo locations + const membersToCoordinates = new Map(); + membersToCoordinates.set( + member1, + new GeospatialData(13.361389, 38.115556), + ); + membersToCoordinates.set( + member2, + new GeospatialData(15.087269, 37.502669), + ); + expect(await client.geoadd(key1, membersToCoordinates)).toBe(2); + + // checking result with default metric + expect( + await client.geodist(key1, member1, member2), + ).toBeCloseTo(expected, delta); + + // checking result with metric specification of kilometers + expect( + await client.geodist( + key1, + member1, + member2, + GeoUnit.KILOMETERS, + ), + ).toBeCloseTo(expectedKM, delta); + + // null result when member index is missing + expect( + await client.geodist(key1, member1, nonExistingMember), + ).toBeNull(); + + // key exists but holds non-ZSET value + expect(await client.set(key2, "geodist")).toBe("OK"); + await expect( + client.geodist(key2, member1, member2), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); } export function runCommonTests(config: { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index bb34980827..c23fa5a485 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -10,6 +10,7 @@ import { BaseClient, BaseClientConfiguration, ClusterTransaction, + GeoUnit, GlideClient, GlideClusterClient, InsertPosition, @@ -672,6 +673,10 @@ export async function transactionTest( [13.36138933897018433, 38.11555639549629859], [15.08726745843887329, 37.50266842333162032], ]); + baseTransaction.geodist(key18, "Palermo", "Catania"); + args.push(166274.1516); + baseTransaction.geodist(key18, "Palermo", "Catania", GeoUnit.KILOMETERS); + args.push(166.2742); const libName = "mylib1C" + uuidv4().replaceAll("-", ""); const funcName = "myfunc1c" + uuidv4().replaceAll("-", "");