From da42822dca2207650a56a3edf2d4311c00628525 Mon Sep 17 00:00:00 2001 From: Aaron <69273634+aaron-congo@users.noreply.github.com> Date: Mon, 10 Jun 2024 18:22:47 -0700 Subject: [PATCH] Node: add LINSERT command (#1544) * Node: add LINSERT command * PR suggestions * Use backticks for cross-references * Fix doc --------- Co-authored-by: Andrew Carbonetto --- CHANGELOG.md | 1 + node/npm/glide/index.ts | 2 ++ node/src/BaseClient.ts | 33 +++++++++++++++++++ node/src/Commands.ts | 26 +++++++++++++++ node/src/Transaction.ts | 26 +++++++++++++++ node/tests/SharedTests.ts | 64 +++++++++++++++++++++++++++++++++++++ node/tests/TestUtilities.ts | 8 +++++ 7 files changed, 160 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d664ac79e..522baa4a9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * Node: Added SUNIONSTORE command ([#1549](https://github.com/aws/glide-for-redis/pull/1549)) * Node: Added PFCOUNT command ([#1545](https://github.com/aws/glide-for-redis/pull/1545)) * Node: Added OBJECT FREQ command ([#1542](https://github.com/aws/glide-for-redis/pull/1542)) +* Node: Added LINSERT command ([#1544](https://github.com/aws/glide-for-redis/pull/1544)) ### Breaking Changes * Node: Update XREAD to return a Map of Map ([#1494](https://github.com/aws/glide-for-redis/pull/1494)) diff --git a/node/npm/glide/index.ts b/node/npm/glide/index.ts index 5dcff693b6..f5cbf82d0b 100644 --- a/node/npm/glide/index.ts +++ b/node/npm/glide/index.ts @@ -87,6 +87,7 @@ function initialize() { Logger, ExpireOptions, InfoOptions, + InsertPosition, SetOptions, ZaddOptions, ScoreBoundry, @@ -129,6 +130,7 @@ function initialize() { Logger, ExpireOptions, InfoOptions, + InsertPosition, SetOptions, ZaddOptions, ScoreBoundry, diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 912e347121..a3b50f1c27 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -13,6 +13,7 @@ import { Buffer, BufferWriter, Reader, Writer } from "protobufjs"; import { AggregationType, ExpireOptions, + InsertPosition, KeyWeight, RangeByIndex, RangeByLex, @@ -47,6 +48,7 @@ import { createIncrBy, createIncrByFloat, createLIndex, + createLInsert, createLLen, createLPop, createLPush, @@ -2306,6 +2308,37 @@ export class BaseClient { return this.createWritePromise(createLIndex(key, index)); } + /** + * Inserts `element` in the list at `key` either before or after the `pivot`. + * + * See https://valkey.io/commands/linsert/ for more details. + * + * @param key - The key of the list. + * @param position - The relative position to insert into - either `InsertPosition.Before` or + * `InsertPosition.After` the `pivot`. + * @param pivot - An element of the list. + * @param element - The new element to insert. + * @returns The list length after a successful insert operation. + * If the `key` doesn't exist returns `-1`. + * If the `pivot` wasn't found, returns `0`. + * + * @example + * ```typescript + * const length = await client.linsert("my_list", InsertPosition.Before, "World", "There"); + * console.log(length); // Output: 2 - The list has a length of 2 after performing the insert. + * ``` + */ + public linsert( + key: string, + position: InsertPosition, + pivot: string, + element: string, + ): Promise { + return this.createWritePromise( + createLInsert(key, position, pivot, element), + ); + } + /** Remove the existing timeout on `key`, turning the key from volatile (a key with an expire set) to * persistent (a key that will never expire as no timeout is associated). * See https://redis.io/commands/persist/ for more details. diff --git a/node/src/Commands.ts b/node/src/Commands.ts index d32bfed331..1c55f0df36 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -1110,6 +1110,32 @@ export function createLIndex( return createCommand(RequestType.LIndex, [key, index.toString()]); } +/** + * Defines where to insert new elements into a list. + */ +export enum InsertPosition { + /** + * Insert new element before the pivot. + */ + Before = "before", + /** + * Insert new element after the pivot. + */ + After = "after", +} + +/** + * @internal + */ +export function createLInsert( + key: string, + position: InsertPosition, + pivot: string, + element: string, +): redis_request.Command { + return createCommand(RequestType.LInsert, [key, position, pivot, element]); +} + /** * @internal */ diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 8855900add..5041b36637 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -6,6 +6,7 @@ import { AggregationType, ExpireOptions, InfoOptions, + InsertPosition, KeyWeight, RangeByIndex, RangeByLex, @@ -49,6 +50,7 @@ import { createIncrByFloat, createInfo, createLIndex, + createLInsert, createLLen, createLPop, createLPush, @@ -1266,6 +1268,30 @@ export class BaseTransaction> { return this.addAndReturn(createLIndex(key, index)); } + /** + * Inserts `element` in the list at `key` either before or after the `pivot`. + * + * See https://valkey.io/commands/linsert/ for more details. + * + * @param key - The key of the list. + * @param position - The relative position to insert into - either `InsertPosition.Before` or + * `InsertPosition.After` the `pivot`. + * @param pivot - An element of the list. + * @param element - The new element to insert. + * + * Command Response - The list length after a successful insert operation. + * If the `key` doesn't exist returns `-1`. + * If the `pivot` wasn't found, returns `0`. + */ + public linsert( + key: string, + position: InsertPosition, + pivot: string, + element: string, + ): T { + return this.addAndReturn(createLInsert(key, position, pivot, element)); + } + /** * Adds an entry to the specified stream stored at `key`. If the `key` doesn't exist, the stream is created. * See https://redis.io/commands/xadd/ for more details. diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index f01bc0c70b..01c2ffdb44 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -9,6 +9,7 @@ import { ClosingError, ExpireOptions, InfoOptions, + InsertPosition, ProtocolVersion, RedisClient, RedisClusterClient, @@ -2176,6 +2177,69 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `linsert test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const stringKey = uuidv4(); + const nonExistingKey = uuidv4(); + + expect(await client.lpush(key1, ["4", "3", "2", "1"])).toEqual( + 4, + ); + expect( + await client.linsert( + key1, + InsertPosition.Before, + "2", + "1.5", + ), + ).toEqual(5); + expect( + await client.linsert( + key1, + InsertPosition.After, + "3", + "3.5", + ), + ).toEqual(6); + expect(await client.lrange(key1, 0, -1)).toEqual([ + "1", + "1.5", + "2", + "3", + "3.5", + "4", + ]); + + expect( + await client.linsert( + key1, + InsertPosition.Before, + "nonExistingPivot", + "4", + ), + ).toEqual(-1); + expect( + await client.linsert( + nonExistingKey, + InsertPosition.Before, + "pivot", + "elem", + ), + ).toEqual(0); + + // key exists, but it is not a list + expect(await client.set(stringKey, "value")).toEqual("OK"); + await expect( + client.linsert(stringKey, InsertPosition.Before, "a", "b"), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `zpopmin test_%p`, async (protocol) => { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 9be9f360a8..c56f6965a6 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -10,6 +10,7 @@ import { BaseClient, BaseClientConfiguration, ClusterTransaction, + InsertPosition, Logger, ProtocolVersion, RedisClient, @@ -295,6 +296,13 @@ export async function transactionTest( args.push([field + "3", field + "2"]); baseTransaction.lpopCount(key5, 2); args.push([field + "3", field + "2"]); + baseTransaction.linsert( + key5, + InsertPosition.Before, + "nonExistingPivot", + "element", + ); + args.push(0); baseTransaction.rpush(key6, [field + "1", field + "2", field + "3"]); args.push(3); baseTransaction.lindex(key6, 0);