Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node: Add LCS command. #2049

Merged
merged 5 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#### Changes
* Node: Added LCS command ([#2049](https://github.com/valkey-io/valkey-glide/pull/2049))
* Node: Added MSETNX command ([#2046](https://github.com/valkey-io/valkey-glide/pull/2046))
* Node: Added BLMOVE command ([#2027](https://github.com/valkey-io/valkey-glide/pull/2027))
* Node: Exported client configuration types ([#2023](https://github.com/valkey-io/valkey-glide/pull/2023))
Expand Down
4 changes: 2 additions & 2 deletions node/npm/glide/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ function initialize() {
ListDirection,
ExpireOptions,
FlushMode,
GeoUnit,
InfoOptions,
InsertPosition,
SetOptions,
Expand All @@ -138,6 +137,7 @@ function initialize() {
ConfigurationError,
ExecAbortError,
RedisError,
ReturnType,
RequestError,
TimeoutError,
ConnectionError,
Expand Down Expand Up @@ -199,7 +199,6 @@ function initialize() {
ListDirection,
ExpireOptions,
FlushMode,
GeoUnit,
InfoOptions,
InsertPosition,
SetOptions,
Expand All @@ -221,6 +220,7 @@ function initialize() {
ConfigurationError,
ExecAbortError,
RedisError,
ReturnType,
RequestError,
TimeoutError,
ConnectionError,
Expand Down
113 changes: 108 additions & 5 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
GeoUnit,
GeospatialData,
InsertPosition,
KeyWeight,
KeyWeight, // eslint-disable-line @typescript-eslint/no-unused-vars
LPosOptions,
ListDirection,
MemberOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -84,6 +84,7 @@ import {
createIncr,
createIncrBy,
createIncrByFloat,
createLCS,
createLIndex,
createLInsert,
createLLen,
Expand Down Expand Up @@ -3940,11 +3941,8 @@ export class BaseClient {
* where each sub-array represents a single item in the following order:
*
* - The member (location) name.
*
* - The distance from the center as a floating point `number`, in the same unit specified for `searchBy`, if `withDist` is set to `true`.
*
* - The geohash of the location as a integer `number`, if `withHash` is set to `true`.
*
* - The coordinates as a two item `array` of floating point `number`s, if `withCoord` is set to `true`.
*
* @example
Expand Down Expand Up @@ -4173,7 +4171,7 @@ export class BaseClient {
* See https://valkey.io/commands/geohash/ for more details.
*
* @param key - The key of the sorted set.
* @param members - The array of members whose <code>GeoHash</code> strings are to be retrieved.
* @param members - The array of members whose `GeoHash` strings are to be retrieved.
* @returns An array of `GeoHash` strings representing the positions of the specified members stored at `key`.
* If a member does not exist in the sorted set, a `null` value is returned for that member.
*
Expand All @@ -4191,6 +4189,111 @@ export class BaseClient {
);
}

/**
* Returns all the longest common subsequences combined between strings stored at `key1` and `key2`.
*
* since Valkey version 7.0.0.
*
* @remarks When in cluster mode, `key1` and `key2` must map to the same hash slot.
*
* See https://valkey.io/commands/lcs/ for more details.
*
* @param key1 - The key that stores the first string.
* @param key2 - The key that stores the second string.
* @returns A `String` containing all the longest common subsequence combined between the 2 strings.
* An empty `String` is returned if the keys do not exist or have no common subsequences.
*
* @example
* ```typescript
* await client.mset({"testKey1": "abcd", "testKey2": "axcd"});
* const result = await client.lcs("testKey1", "testKey2");
* console.log(result); // Output: 'cd'
* ```
*/
public async lcs(key1: string, key2: string): Promise<string> {
return this.createWritePromise(createLCS(key1, key2));
}

/**
* Returns the total length of all the longest common subsequences between strings stored at `key1` and `key2`.
*
* since Valkey version 7.0.0.
*
* @remarks When in cluster mode, `key1` and `key2` must map to the same hash slot.
*
* See https://valkey.io/commands/lcs/ for more details.
*
* @param key1 - The key that stores the first string.
* @param key2 - The key that stores the second string.
* @returns The total length of all the longest common subsequences between the 2 strings.
*
* @example
* ```typescript
* await client.mset({"testKey1": "abcd", "testKey2": "axcd"});
* const result = await client.lcsLen("testKey1", "testKey2");
* console.log(result); // Output: 2
* ```
*/
public async lcsLen(key1: string, key2: string): Promise<number> {
return this.createWritePromise(createLCS(key1, key2, { len: true }));
}

/**
* Returns the indices and lengths of the longest common subsequences between strings stored at
* `key1` and `key2`.
*
* since Valkey version 7.0.0.
*
* @remarks When in cluster mode, `key1` and `key2` must map to the same hash slot.
*
* See https://valkey.io/commands/lcs/ for more details.
*
* @param key1 - The key that stores the first string.
* @param key2 - The key that stores the second string.
* @param withMatchLen - (Optional) If `true`, include the length of the substring matched for the each match.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* @param minMatchLen - (Optional) The minimum length of matches to include in the result.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* @returns A `Record` containing the indices of the longest common subsequences between the
* 2 strings and the lengths of the longest common subsequences. The resulting map contains two
* keys, "matches" and "len":
* - `"len"` is mapped to the total length of the all longest common subsequences between the 2 strings
* stored as an integer. This value doesn't count towards the `minMatchLen` filter.
* - `"matches"` is mapped to a three dimensional array of integers that stores pairs
* of indices that represent the location of the common subsequences in the strings held
* by `key1` and `key2`.
*
* @example
* ```typescript
* await client.mset({"key1": "ohmytext", "key2": "mynewtext"});
* const result = await client.lcsIdx("key1", "key2");
* console.log(result); // Output:
* {
* "matches" :
* [
* [ // first substring match is "text"
* [4, 7], // in `key1` it is located between indices 4 and 7
* [5, 8], // and in `key2` - in between 5 and 8
* 4 // the match length, returned if `withMatchLen` set to `true`
* ],
* [ // second substring match is "my"
* [2, 3], // in `key1` it is located between indices 2 and 3
* [0, 1], // and in `key2` - in between 0 and 1
* 2 // the match length, returned if `withMatchLen` set to `true`
* ]
* ],
* "len" : 6 // total length of the all matches found
* }
* ```
*/
public async lcsIdx(
key1: string,
key2: string,
options?: { withMatchLen?: boolean; minMatchLen?: number },
): Promise<Record<string, (number | [number, number])[][] | number>> {
return this.createWritePromise(
createLCS(key1, key2, { idx: options ?? {} }),
);
}

/**
* @internal
*/
Expand Down
24 changes: 24 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2776,3 +2776,27 @@ export function createZRandMember(

return createCommand(RequestType.ZRandMember, args);
}

/** @internal */
export function createLCS(
key1: string,
key2: string,
options?: {
len?: boolean;
idx?: { withMatchLen?: boolean; minMatchLen?: number };
},
): command_request.Command {
const args = [key1, key2];

if (options) {
if (options.len) args.push("LEN");
else if (options.idx) {
args.push("IDX");
if (options.idx.withMatchLen) args.push("WITHMATCHLEN");
if (options.idx.minMatchLen !== undefined)
args.push("MINMATCHLEN", options.idx.minMatchLen.toString());
}
}

return createCommand(RequestType.LCS, args);
}
69 changes: 65 additions & 4 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import {
createIncrBy,
createIncrByFloat,
createInfo,
createLCS,
createLIndex,
createLInsert,
createLLen,
Expand Down Expand Up @@ -2364,11 +2365,8 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
* where each sub-array represents a single item in the following order:
*
* - The member (location) name.
*
* - The distance from the center as a floating point `number`, in the same unit specified for `searchBy`.
*
* - The geohash of the location as a integer `number`.
*
* - The coordinates as a two item `array` of floating point `number`s.
*/
public geosearch(
Expand Down Expand Up @@ -2496,14 +2494,77 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
* See https://valkey.io/commands/geohash/ for more details.
*
* @param key - The key of the sorted set.
* @param members - The array of members whose <code>GeoHash</code> strings are to be retrieved.
* @param members - The array of members whose `GeoHash` strings are to be retrieved.
*
* Command Response - An array of `GeoHash` strings representing the positions of the specified members stored at `key`.
* If a member does not exist in the sorted set, a `null` value is returned for that member.
*/
public geohash(key: string, members: string[]): T {
return this.addAndReturn(createGeoHash(key, members));
}

/**
* Returns all the longest common subsequences combined between strings stored at `key1` and `key2`.
*
* since Valkey version 7.0.0.
*
* See https://valkey.io/commands/lcs/ for more details.
*
* @param key1 - The key that stores the first string.
* @param key2 - The key that stores the second string.
*
* Command Response - A `String` containing all the longest common subsequence combined between the 2 strings.
* An empty `String` is returned if the keys do not exist or have no common subsequences.
*/
public lcs(key1: string, key2: string): T {
return this.addAndReturn(createLCS(key1, key2));
}

/**
* Returns the total length of all the longest common subsequences between strings stored at `key1` and `key2`.
*
* since Valkey version 7.0.0.
*
* See https://valkey.io/commands/lcs/ for more details.
*
* @param key1 - The key that stores the first string.
* @param key2 - The key that stores the second string.
*
* Command Response - The total length of all the longest common subsequences between the 2 strings.
*/
public lcsLen(key1: string, key2: string): T {
return this.addAndReturn(createLCS(key1, key2, { len: true }));
}

/**
* Returns the indices and lengths of the longest common subsequences between strings stored at
* `key1` and `key2`.
*
* since Valkey version 7.0.0.
*
* See https://valkey.io/commands/lcs/ for more details.
*
* @param key1 - The key that stores the first string.
* @param key2 - The key that stores the second string.
* @param withMatchLen - (Optional) If `true`, include the length of the substring matched for the each match.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* @param minMatchLen - (Optional) The minimum length of matches to include in the result.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*
* Command Response - A `Record` containing the indices of the longest common subsequences between the
* 2 strings and the lengths of the longest common subsequences. The resulting map contains two
* keys, "matches" and "len":
* - `"len"` is mapped to the total length of the all longest common subsequences between the 2 strings
* stored as an integer. This value doesn't count towards the `minMatchLen` filter.
* - `"matches"` is mapped to a three dimensional array of integers that stores pairs
* of indices that represent the location of the common subsequences in the strings held
* by `key1` and `key2`.
*/
public lcsIdx(
key1: string,
key2: string,
options?: { withMatchLen?: boolean; minMatchLen?: number },
): T {
return this.addAndReturn(createLCS(key1, key2, { idx: options ?? {} }));
}
}

/**
Expand Down
11 changes: 4 additions & 7 deletions node/tests/RedisClusterClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,17 +338,14 @@ describe("GlideClusterClient", () => {
client.zintercard(["abc", "zxy", "lkn"]),
client.zmpop(["abc", "zxy", "lkn"], ScoreFilter.MAX),
client.bzmpop(["abc", "zxy", "lkn"], ScoreFilter.MAX, 0.1),
client.lcs("abc", "xyz"),
client.lcsLen("abc", "xyz"),
client.lcsIdx("abc", "xyz"),
);
}

for (const promise of promises) {
try {
await promise;
} catch (e) {
expect((e as Error).message.toLowerCase()).toContain(
"crossslot",
);
}
await expect(promise).rejects.toThrowError(/crossslot/i);
}

client.close();
Expand Down
Loading
Loading