From b778d3509fd626026bacb614d5310cb1654ff979 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Thu, 25 Jul 2024 15:26:07 -0700 Subject: [PATCH] Node: Add `ZINCRBY` command (#2009) * Node: Add ZINCRBY command --------- Signed-off-by: Andrew Carbonetto --- CHANGELOG.md | 1 + node/src/BaseClient.ts | 34 ++++++++++++++++++++++ node/src/Commands.ts | 15 ++++++++++ node/src/Transaction.ts | 18 ++++++++++++ node/tests/SharedTests.ts | 33 +++++++++++++++++++++ node/tests/TestUtilities.ts | 2 ++ python/python/glide/async_commands/core.py | 4 +-- 7 files changed, 105 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d39d8ac5ba..857f9725ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ * Node: Added FUNCTION FLUSH command ([#1984](https://github.com/valkey-io/valkey-glide/pull/1984)) * Node: Added FCALL and FCALL_RO commands ([#2011](https://github.com/valkey-io/valkey-glide/pull/2011)) * Node: Added ZMPOP command ([#1994](https://github.com/valkey-io/valkey-glide/pull/1994)) +* Node: Added ZINCRBY command ([#2009](https://github.com/valkey-io/valkey-glide/pull/2009)) #### Fixes * Java: Add overloads for XADD to allow duplicate entry keys ([#1970](https://github.com/valkey-io/valkey-glide/pull/1970)) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 0808339c44..ac62c35bcd 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -123,6 +123,7 @@ import { createZDiff, createZDiffStore, createZDiffWithScores, + createZIncrBy, createZInterCard, createZInterstore, createZMPop, @@ -3695,6 +3696,39 @@ export class BaseClient { return this.createWritePromise(createZMPop(key, modifier, count)); } + /** + * Increments the score of `member` in the sorted set stored at `key` by `increment`. + * If `member` does not exist in the sorted set, it is added with `increment` as its score. + * If `key` does not exist, a new sorted set is created with the specified member as its sole member. + * + * See https://valkey.io/commands/zincrby/ for details. + * + * @param key - The key of the sorted set. + * @param increment - The score increment. + * @param member - A member of the sorted set. + * + * @returns The new score of `member`. + * + * @example + * ```typescript + * // Example usage of zincrBy method to increment the value of a member's score + * await client.zadd("my_sorted_set", {"member": 10.5, "member2": 8.2}); + * console.log(await client.zincrby("my_sorted_set", 1.2, "member")); + * // Output: 11.7 - The member existed in the set before score was altered, the new score is 11.7. + * console.log(await client.zincrby("my_sorted_set", -1.7, "member")); + * // Output: 10.0 - Negative increment, decrements the score. + * console.log(await client.zincrby("my_sorted_set", 5.5, "non_existing_member")); + * // Output: 5.5 - A new member is added to the sorted set with the score of 5.5. + * ``` + */ + public async zincrby( + key: string, + increment: number, + member: string, + ): Promise { + return this.createWritePromise(createZIncrBy(key, increment, member)); + } + /** * Returns the distance between `member1` and `member2` saved in the geospatial index stored at `key`. * diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 0a85954fde..044ddc9759 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -2082,3 +2082,18 @@ export function createZMPop( return createCommand(RequestType.ZMPop, args); } + +/** + * @internal + */ +export function createZIncrBy( + key: string, + increment: number, + member: string, +): command_request.Command { + return createCommand(RequestType.ZIncrBy, [ + key, + increment.toString(), + member, + ]); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 8521779848..9c4aaeb347 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -151,6 +151,7 @@ import { createZRevRank, createZRevRankWithScore, createZScore, + createZIncrBy, } from "./Commands"; import { command_request } from "./ProtobufMessage"; import { BitOffsetOptions } from "./commands/BitOffsetOptions"; @@ -2181,6 +2182,23 @@ export class BaseTransaction> { return this.addAndReturn(createZMPop(keys, modifier, count)); } + /** + * Increments the score of `member` in the sorted set stored at `key` by `increment`. + * If `member` does not exist in the sorted set, it is added with `increment` as its score. + * If `key` does not exist, a new sorted set is created with the specified member as its sole member. + * + * See https://valkey.io/commands/zincrby/ for details. + * + * @param key - The key of the sorted set. + * @param increment - The score increment. + * @param member - A member of the sorted set. + * + * Command Response - The new score of `member`. + */ + public zincrby(key: string, increment: number, member: string): T { + return this.addAndReturn(createZIncrBy(key, increment, member)); + } + /** * Returns the distance between `member1` and `member2` saved in the geospatial index stored at `key`. * diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 564ce1bd6e..e0bd789c40 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -4968,6 +4968,39 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zincrby test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = "{key}" + uuidv4(); + const member = "{member}-1" + uuidv4(); + const othermember = "{member}-1" + uuidv4(); + const stringKey = "{key}-string" + uuidv4(); + + // key does not exist + expect(await client.zincrby(key, 2.5, member)).toEqual(2.5); + expect(await client.zscore(key, member)).toEqual(2.5); + + // key exists, but value doesn't + expect(await client.zincrby(key, -3.3, othermember)).toEqual( + -3.3, + ); + expect(await client.zscore(key, othermember)).toEqual(-3.3); + + // updating existing value in existing key + expect(await client.zincrby(key, 1.0, member)).toEqual(3.5); + expect(await client.zscore(key, member)).toEqual(3.5); + + // Key exists, but it is not a sorted set + expect(await client.set(stringKey, "value")).toEqual("OK"); + await expect( + client.zincrby(stringKey, 0.5, "_"), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `geodist test_%p`, async (protocol) => { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index d158350556..fbe9722921 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -613,6 +613,8 @@ export async function transactionTest( baseTransaction.zaddIncr(key8, "member2", 1); responseData.push(['zaddIncr(key8, "member2", 1)', 3]); + baseTransaction.zincrby(key8, 0.3, "member1"); + responseData.push(['zincrby(key8, 0.3, "member1")', 1.3]); baseTransaction.zrem(key8, ["member1"]); responseData.push(['zrem(key8, ["member1"])', 1]); baseTransaction.zcard(key8); diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 3589f8d340..a267c73d25 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -4202,9 +4202,9 @@ async def zincrby( >>> await client.zincrby("my_sorted_set", 1.2, "member") 11.7 # The member existed in the set before score was altered, the new score is 11.7. >>> await client.zincrby("my_sorted_set", -1.7, "member") - 10.0 # Negetive increment, decrements the score. + 10.0 # Negative increment, decrements the score. >>> await client.zincrby("my_sorted_set", 5.5, "non_existing_member") - 5.5 # A new memeber is added to the sorted set with the score being 5.5. + 5.5 # A new member is added to the sorted set with the score being 5.5. """ return cast( float,