From 5d394d0597c8798d5067218b85c7aa32249d7097 Mon Sep 17 00:00:00 2001 From: Adan Date: Sun, 8 Oct 2023 11:07:21 +0000 Subject: [PATCH] added hincrBy and hincrByFloat in node --- node/src/BaseClient.ts | 42 +++++++++++++++++ node/src/Commands.ts | 24 ++++++++++ node/src/Transaction.ts | 36 +++++++++++++++ node/tests/SharedTests.ts | 96 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 808840d57a..2d69315521 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -13,6 +13,8 @@ import { createGet, createHDel, createHGet, + createHIncrBy, + createHIncrByFloat, createHMGet, createHSet, createIncr, @@ -425,6 +427,7 @@ export class BaseClient { /** Removes the specified fields from the hash stored at key. * Specified fields that do not exist within this hash are ignored. + * See https://redis.io/commands/hdel/ for details. * * @param key - The key of the hash. * @param fields - The fields to remove from the hash stored at key. @@ -436,6 +439,7 @@ export class BaseClient { } /** Returns the values associated with the specified fields in the hash stored at key. + * See https://redis.io/commands/hmget/ for details. * * @param key - The key of the hash. * @param fields - The fields in the hash stored at key to retrieve from the database. @@ -447,6 +451,44 @@ export class BaseClient { return this.createWritePromise(createHMGet(key, fields)); } + /** Increments the number stored at field in the hash stored at key by increment. + * If the field or the key does not exist, it is set to 0 before performing the operation. + * See https://redis.io/commands/hincrby/ for details. + * + * @param key - The key of the hash. + * @param amount - The amount to increment. + * @param field - The field in the hash stored at key to increment it's value. + * @returns the value of the field in the hash stored at key after the increment, An error is returned if the key contains a value + * of the wrong type or contains a string that can not be represented as integer. + */ + public hincrBy( + key: string, + field: string, + amount: number + ): Promise { + return this.createWritePromise(createHIncrBy(key, field, amount)); + } + + /** Increment the string representing a floating point number stored at field in the hash stored at key by increment. + * By using a negative increment value, the result is that the value stored at field in the hash stored at key is decremented. + * If the field or the key does not exist, it is set to 0 before performing the operation. + * See https://redis.io/commands/hincrbyfloat/ for details. + * + * @param key - The key of the hash. + * @param amount - The amount to increment. + * @param field - The field in the hash stored at key to increment it's value. + * @returns the value of the field in the hash stored at key after the increment as string, + * An error is returned if the key contains a value of the wrong type. + * + */ + public hincrByFloat( + key: string, + field: string, + amount: number + ): Promise { + return this.createWritePromise(createHIncrByFloat(key, field, amount)); + } + private readonly MAP_READ_FROM_REPLICA_STRATEGY: Record< ReadFromReplicaStrategy, connection_request.ReadFromReplicaStrategy diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 335208bb89..657d0a87b4 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -329,3 +329,27 @@ export function createHMGet( export function createCustomCommand(commandName: string, args: string[]) { return createCommand(RequestType.CustomCommand, [commandName, ...args]); } + +export function createHIncrBy( + key: string, + field: string, + amount: number +): redis_request.Command { + return createCommand(RequestType.HashIncrBy, [ + key, + field, + amount.toString(), + ]); +} + +export function createHIncrByFloat( + key: string, + field: string, + amount: number +): redis_request.Command { + return createCommand(RequestType.HashIncrByFloat, [ + key, + field, + amount.toString(), + ]); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 20ec77b4dd..ddb7cabdbd 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -14,6 +14,8 @@ import { createGet, createHDel, createHGet, + createHIncrBy, + createHIncrByFloat, createHMGet, createHSet, createIncr, @@ -288,6 +290,7 @@ export class BaseTransaction { /** Removes the specified fields from the hash stored at key. * Specified fields that do not exist within this hash are ignored. + * See https://redis.io/commands/hdel/ for details. * * @param key - The key of the hash. * @param fields - The fields to remove from the hash stored at key. @@ -300,6 +303,7 @@ export class BaseTransaction { } /** Returns the values associated with the specified fields in the hash stored at key. + * See https://redis.io/commands/hmget/ for details. * * @param key - The key of the hash. * @param fields - The fields in the hash stored at key to retrieve from the database. @@ -312,6 +316,38 @@ export class BaseTransaction { this.commands.push(createHMGet(key, fields)); } + /** Increments the number stored at field in the hash stored at key by increment. + * If the field or the key does not exist, it is set to 0 before performing the operation. + * See https://redis.io/commands/hincrby/ for details. + * + * @param key - The key of the hash. + * @param amount - The amount to increment. + * @param field - The field in the hash stored at key to increment it's value. + * + * Command Response - the value of the field in the hash stored at key after the increment , An error is returned if the key contains a value + * of the wrong type or contains a string that can not be represented as integer. + */ + public hincrBy(key: string, field: string, amount: number) { + this.commands.push(createHIncrBy(key, field, amount)); + } + + /** Increment the string representing a floating point number stored at field in the hash stored at key by increment. + * By using a negative increment value, the result is that the value stored at field in the hash stored at key is decremented. + * If the field or the key does not exist, it is set to 0 before performing the operation. + * See https://redis.io/commands/hincrbyfloat/ for details. + * + * @param key - The key of the hash. + * @param amount - The amount to increment. + * @param field - The field in the hash stored at key to increment it's value. + * + * Command Response - the value of the field in the hash stored at key after the increment as string, + * An error is returned if the key contains a value of the wrong type. + * + */ + public hincrByFloat(key: string, field: string, amount: number) { + this.commands.push(createHIncrByFloat(key, field, amount)); + } + /** Executes a single command, without checking inputs. Every part of the command, including subcommands, * should be added as a separate value in args. * diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 6429fbd95f..5aa629483b 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -35,6 +35,12 @@ type BaseClient = { hget: (key: string, field: string) => Promise; hdel: (key: string, fields: string[]) => Promise; hmget: (key: string, fields: string[]) => Promise<(string | null)[]>; + hincrBy: (key: string, field: string, amount: number) => Promise; + hincrByFloat: ( + key: string, + field: string, + amount: number + ) => Promise; customCommand: (commandName: string, args: string[]) => Promise; }; @@ -555,6 +561,96 @@ export function runBaseTests(config: { }, config.timeout ); + + it( + "hincrBy and hincrByFloat with existing key and field", + async () => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const field = uuidv4(); + const fieldValueMap = { + [field]: "10", + }; + expect(await client.hset(key, fieldValueMap)).toEqual(1); + expect(await client.hincrBy(key, field, 1)).toEqual(11); + expect(await client.hget(key, field)).toEqual("11"); + expect(await client.hincrBy(key, field, 4)).toEqual(15); + expect(await client.hget(key, field)).toEqual("15"); + expect(await client.hincrByFloat(key, field, 1.5)).toEqual( + "16.5" + ); + expect(await client.hget(key, field)).toEqual("16.5"); + }); + }, + config.timeout + ); + + it( + "hincrBy and hincrByFloat with non existing key and non existing field", + async () => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + const nonExistingKey1 = uuidv4(); + const field = uuidv4(); + const nonExistingField = uuidv4(); + const fieldValueMap = { + [field]: "10", + }; + expect(await client.hincrBy(nonExistingKey1, field, 1)).toEqual( + 1 + ); + expect(await client.hget(nonExistingKey1, field)).toEqual("1"); + + expect(await client.hset(key1, fieldValueMap)).toEqual(1); + expect(await client.hincrBy(key1, nonExistingField, 2)).toEqual( + 2 + ); + expect(await client.hget(key1, nonExistingField)).toEqual("2"); + + expect(await client.hset(key2, fieldValueMap)).toEqual(1); + expect( + await client.hincrByFloat(key2, nonExistingField, -0.5) + ).toEqual("-0.5"); + expect(await client.hget(key2, nonExistingField)).toEqual( + "-0.5" + ); + }); + }, + config.timeout + ); + + it( + "hincrBy and hincrByFloat with a field that contains a value of string that can not be represented as integer", + async () => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const field = uuidv4(); + const fieldValueMap = { + [field]: "foo", + }; + expect(await client.hset(key, fieldValueMap)).toEqual(1); + try { + expect(await client.hincrBy(key, field, 2)).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "hash value is not an integer" + ); + } + + try { + expect( + await client.hincrByFloat(key, field, 1.5) + ).toThrow(); + } catch (e) { + expect((e as Error).message).toMatch( + "hash value is not a float" + ); + } + }); + }, + config.timeout + ); } export function runCommonTests(config: {