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

Added zadd and zaddIncr in node. #814

Merged
merged 1 commit into from
Jan 18, 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
67 changes: 67 additions & 0 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Buffer, BufferWriter, Reader, Writer } from "protobufjs";
import {
ExpireOptions,
SetOptions,
ZaddOptions,
createDecr,
createDecrBy,
createDel,
Expand Down Expand Up @@ -46,6 +47,7 @@ import {
createSet,
createTTL,
createUnlink,
createZadd,
} from "./Commands";
import {
ClosingError,
Expand Down Expand Up @@ -945,6 +947,71 @@ export class BaseClient {
return this.createWritePromise(scriptInvocation);
}

/** Adds members with their scores to the sorted set stored at `key`.
* If a member is already a part of the sorted set, its score is updated.
* See https://redis.io/commands/zadd/ for more details.
*
* @param key - The key of the sorted set.
* @param membersScoresMap - A mapping of members to their corresponding scores.
* @param options - The Zadd options.
* @param changed - Modify the return value from the number of new elements added, to the total number of elements changed.
* @returns The number of elements added to the sorted set.
* If `changed` is set, returns the number of elements updated in the sorted set.
*
* @example
* await zadd("mySortedSet", \{ "member1": 10.5, "member2": 8.2 \})
* 2 (Indicates that two elements have been added or updated in the sorted set "mySortedSet".)
*
* await zadd("existingSortedSet", \{ member1: 15.0, member2: 5.5 \}, \{ conditionalChange: "onlyIfExists" \});
* 2 (Updates the scores of two existing members in the sorted set "existingSortedSet".)
*
*/
public zadd(
key: string,
membersScoresMap: Record<string, number>,
options?: ZaddOptions,
changed?: boolean
): Promise<number> {
return this.createWritePromise(
createZadd(
key,
membersScoresMap,
options,
changed ? "CH" : undefined
)
);
}

/** 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 (as if its previous score was 0.0).
* If `key` does not exist, a new sorted set with the specified member as its sole member is created.
* See https://redis.io/commands/zadd/ for more details.
*
* @param key - The key of the sorted set.
* @param member - A member in the sorted set to increment.
* @param increment - The score to increment the member.
* @param options - The Zadd options.
* @returns The score of the member.
* If there was a conflict with the options, the operation aborts and null is returned.
*
* @example
* await zaddIncr("mySortedSet", member , 5.0)
* 5.0
*
* await zaddIncr("existingSortedSet", member , "3.0" , \{ UpdateOptions: "ScoreLessThanCurrent" \})
* null
*/
public zaddIncr(
key: string,
member: string,
increment: number,
options?: ZaddOptions
): Promise<number | null> {
return this.createWritePromise(
createZadd(key, { [member]: increment }, options, "INCR")
);
}

private readonly MAP_READ_FROM_STRATEGY: Record<
ReadFrom,
connection_request.ReadFrom
Expand Down
59 changes: 59 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,3 +680,62 @@ export function createPExpireAt(
export function createTTL(key: string): redis_request.Command {
return createCommand(RequestType.TTL, [key]);
}

export type ZaddOptions = {
/**
* `onlyIfDoesNotExist` - Only add new elements. Don't update already existing elements. Equivalent to `NX` in the Redis API.
* `onlyIfExists` - Only update elements that already exist. Don't add new elements. Equivalent to `XX` in the Redis API.
*/
conditionalChange?: "onlyIfExists" | "onlyIfDoesNotExist";
/**
* `scoreLessThanCurrent` - Only update existing elements if the new score is less than the current score.
* Equivalent to `LT` in the Redis API.
* `scoreGreaterThanCurrent` - Only update existing elements if the new score is greater than the current score.
* Equivalent to `GT` in the Redis API.
*/
updateOptions?: "scoreLessThanCurrent" | "scoreGreaterThanCurrent";
};

/**
* @internal
*/
export function createZadd(
key: string,
membersScoresMap: Record<string, number>,
options?: ZaddOptions,
changedOrIncr?: "CH" | "INCR"
): redis_request.Command {
let args = [key];

if (options) {
if (options.conditionalChange === "onlyIfExists") {
args.push("XX");
} else if (options.conditionalChange === "onlyIfDoesNotExist") {
if (options.updateOptions) {
throw new Error(
`The GT, LT, and NX options are mutually exclusive. Cannot choose both ${options.updateOptions} and NX.`
);
}

args.push("NX");
}

if (options.updateOptions === "scoreLessThanCurrent") {
args.push("LT");
} else if (options.updateOptions === "scoreGreaterThanCurrent") {
args.push("GT");
}
}

if (changedOrIncr) {
args.push(changedOrIncr);
}

args = args.concat(
Object.entries(membersScoresMap).flatMap(([key, value]) => [
value.toString(),
key,
])
);
return createCommand(RequestType.Zadd, args);
}
96 changes: 96 additions & 0 deletions node/tests/SharedTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,102 @@ export function runBaseTests<Context>(config: {
},
config.timeout
);

it(
"zadd and zaddIncr test",
async () => {
await runTest(async (client: BaseClient) => {
const key = uuidv4();
const membersScores = { one: 1, two: 2, three: 3 };

expect(await client.zadd(key, membersScores)).toEqual(3);
expect(await client.zaddIncr(key, "one", 2)).toEqual(3.0);
});
},
config.timeout
);

it(
"zadd and zaddIncr with NX XX test",
async () => {
await runTest(async (client: BaseClient) => {
const key = uuidv4();
const membersScores = { one: 1, two: 2, three: 3 };
expect(
await client.zadd(key, membersScores, {
conditionalChange: "onlyIfExists",
})
).toEqual(0);

expect(
await client.zadd(key, membersScores, {
conditionalChange: "onlyIfDoesNotExist",
})
).toEqual(3);

expect(
await client.zaddIncr(key, "one", 5.0, {
conditionalChange: "onlyIfDoesNotExist",
})
).toEqual(null);

expect(
await client.zaddIncr(key, "one", 5.0, {
conditionalChange: "onlyIfExists",
})
).toEqual(6.0);
});
},
config.timeout
);

it(
"zadd and zaddIncr with GT LT test",
async () => {
await runTest(async (client: BaseClient) => {
const key = uuidv4();
const membersScores = { one: -3, two: 2, three: 3 };

expect(await client.zadd(key, membersScores)).toEqual(3);
membersScores["one"] = 10;

expect(
await client.zadd(
key,
membersScores,
{
updateOptions: "scoreGreaterThanCurrent",
},
true
)
).toEqual(1);

expect(
await client.zadd(
key,
membersScores,
{
updateOptions: "scoreLessThanCurrent",
},
true
)
).toEqual(0);

expect(
await client.zaddIncr(key, "one", -3.0, {
updateOptions: "scoreLessThanCurrent",
})
).toEqual(7.0);

expect(
await client.zaddIncr(key, "one", -3.0, {
updateOptions: "scoreGreaterThanCurrent",
})
).toEqual(null);
});
},
config.timeout
);
}

export function runCommonTests<Context>(config: {
Expand Down