diff --git a/CHANGELOG.md b/CHANGELOG.md index f9d6da1493..9ce7e9cfba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * Python, Node: Added LINDEX command ([#1058](https://github.com/aws/glide-for-redis/pull/1058), [#999](https://github.com/aws/glide-for-redis/pull/999)) * Python: Added ZRANK command ([#1065](https://github.com/aws/glide-for-redis/pull/1065)) * Core: Enabled Cluster Mode periodic checks by default ([#1089](https://github.com/aws/glide-for-redis/pull/1089)) +* Node: Added Rename command. ([#1124](https://github.com/aws/glide-for-redis/pull/1124)) #### Features diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 444c2bb991..eeb6df5945 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -134,6 +134,7 @@ enum RequestType { ZRemRangeByScore = 90; Time = 91; Zrank = 92; + Rename = 93; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 153f5224ae..bac3c88a99 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -364,6 +364,7 @@ fn get_command(request: &Command) -> Option { RequestType::ZRemRangeByScore => Some(cmd("ZREMRANGEBYSCORE")), RequestType::Time => Some(cmd("TIME")), RequestType::Zrank => Some(cmd("ZRANK")), + RequestType::Rename => Some(cmd("RENAME")), } } diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 66f4ef9606..f47e66c7a9 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -55,6 +55,7 @@ import { createPttl, createRPop, createRPush, + createRename, createSAdd, createSCard, createSMembers, @@ -1326,6 +1327,21 @@ export class BaseClient { return this.createWritePromise(createPersist(key)); } + /** + * Renames `key` to `newkey`. + * In Cluster mode, both `key` and `newkey` must be in the same hash slot, + * meaning that in practice only keys that have the same hash tag can be reliably renamed in cluster. + * If `newkey` already exists it is overwritten, + * See https://redis.io/commands/rename/ for more details. + * + * @param key - The key to rename. + * @param newKey - The newkey to rename to. + * @returns - If the `key` is successfully set, return "OK". Returns an error when key does not exist + */ + public rename(key: string, newKey: string): Promise<"OK"> { + return this.createWritePromise(createRename(key, newKey)); + } + /** * @internal */ diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 85a889afbe..23b36a2bc5 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -1105,3 +1105,13 @@ export function createXread( return createCommand(RequestType.XRead, args); } + +/** + * @internal + */ +export function createRename( + key: string, + newKey: string, +): redis_request.Command { + return createCommand(RequestType.Rename, [key, newKey]); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 87250fa18e..46420d4d05 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -57,6 +57,7 @@ import { createPttl, createRPop, createRPush, + createRename, createSAdd, createSCard, createSMembers, @@ -1110,6 +1111,21 @@ export class BaseTransaction> { ): T { return this.addAndReturn(createXread(keys_and_ids, options)); } + + /** + * Renames `key` to `newkey`. + * In Cluster mode, both `key` and `newkey` must be in the same hash slot, + * meaning that in practice only keys that have the same hash tag can be reliably renamed in cluster. + * If `newkey` already exists it is overwritten, + * See https://redis.io/commands/rename/ for more details. + * + * @param key - The key to rename. + * @param newKey - The newkey to rename to. + * Command Response - If the `key` is successfully set, return "OK". Returns an error when key does not exist + */ + public rename(key: string, newKey: string): T { + return this.addAndReturn(createRename(key, newKey)); + } } /** diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 1168735365..9b6ac62f2c 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1963,6 +1963,25 @@ export function runBaseTests(config: { }, config.timeout, ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "rename test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + // Making sure both keys will be oart of the same slot + const key = uuidv4() + "{123}"; + const newKey = uuidv4() + "{123}"; + await client.set(key, "value"); + await client.rename(key, newKey); + const result = await client.get(newKey); + expect(result).toEqual("value"); + // If key doesn't exist it should throw, it also test that key has succfully been renamed + await expect(client.rename(key, newKey)).rejects.toThrow(); + client.close(); + }, protocol); + }, + config.timeout, + ); } export function runCommonTests(config: { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index dd387999d9..ede64d3772 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -62,6 +62,7 @@ export function transactionTest( const key7 = "{key}" + uuidv4(); const key8 = "{key}" + uuidv4(); const key9 = "{key}" + uuidv4(); + const key10 = "{key}" + uuidv4(); const field = uuidv4(); const value = uuidv4(); const args: ReturnType[] = []; @@ -191,6 +192,10 @@ export function transactionTest( exact: true, }); args.push(1); + baseTransaction.rename(key9, key10); + args.push("OK"); + baseTransaction.exists([key10]); + args.push(1); return args; }