Skip to content

Commit

Permalink
Add zunion to sdk command (#549)
Browse files Browse the repository at this point in the history
* feat: add zunion command implementation

* test: add zunion tests

* feat: add zunion to pipeline command

* test: add zunion test for pipeline command

---------

Co-authored-by: Oiseje Ojeikere <oiseje.ojeikere@kindredgroup.com>
Co-authored-by: chronark <dev@chronark.com>
  • Loading branch information
3 people authored Oct 17, 2023
1 parent 77aefc4 commit 6460921
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 1 deletion.
33 changes: 33 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/commands/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export * from "./zremrangebyscore.ts";
export * from "./zrevrank.ts";
export * from "./zscan.ts";
export * from "./zscore.ts";
export * from "./zunion.ts";
export * from "./zunionstore.ts";
export * from "./xadd.ts";
export * from "./xrange.ts";
261 changes: 261 additions & 0 deletions pkg/commands/zunion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import { keygen, newHttpClient, randomID } from "../test-utils.ts";

import { afterAll } from "https://deno.land/std@0.177.0/testing/bdd.ts";
import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts";
import { ZUnionCommand } from "./zunion.ts";
import { ZAddCommand } from "./zadd.ts";

const client = newHttpClient();

const { newKey, cleanup } = keygen();
afterAll(cleanup);

Deno.test("command format", async (t) => {
await t.step("without options", async (t) => {
await t.step("builds the correct command", () => {
assertEquals(new ZUnionCommand([1, "key"]).command, [
"zunion",
1,
"key",
]);
});
});
await t.step("with multiple keys", async (t) => {
await t.step("builds the correct command", () => {
assertEquals(
new ZUnionCommand([2, ["key1", "key2"]]).command,
["zunion", 2, "key1", "key2"],
);
});
});
await t.step("with single weight", async (t) => {
await t.step("builds the correct command", () => {
assertEquals(
new ZUnionCommand([1, "key", { weight: 4 }])
.command,
["zunion", 1, "key", "weights", 4],
);
});
});
await t.step("with multiple weights", async (t) => {
await t.step("builds the correct command", () => {
assertEquals(
new ZUnionCommand([2, ["key1", "key2"], {
weights: [2, 3],
}]).command,
[
"zunion",
2,
"key1",
"key2",
"weights",
2,
3,
],
);
});
await t.step("with aggregate", async (t) => {
await t.step("sum", async (t) => {
await t.step("builds the correct command", () => {
assertEquals(
new ZUnionCommand([1, "key", {
aggregate: "sum",
}]).command,
["zunion", 1, "key", "aggregate", "sum"],
);
});
});
await t.step("min", async (t) => {
await t.step("builds the correct command", () => {
assertEquals(
new ZUnionCommand([1, "key", {
aggregate: "min",
}]).command,
["zunion", 1, "key", "aggregate", "min"],
);
});
});
await t.step("max", async (t) => {
await t.step("builds the correct command", () => {
assertEquals(
new ZUnionCommand([1, "key", {
aggregate: "max",
}]).command,
["zunion", 1, "key", "aggregate", "max"],
);
});
});
});
await t.step("complex", async (t) => {
await t.step("builds the correct command", () => {
assertEquals(
new ZUnionCommand([2, ["key1", "key2"], {
weights: [4, 2],
aggregate: "max",
}]).command,
[
"zunion",
2,
"key1",
"key2",
"weights",
4,
2,
"aggregate",
"max",
],
);
});
});
});
});

Deno.test("without options", async (t) => {
await t.step("returns the union", async () => {
const key1 = newKey();
const key2 = newKey();
const score1 = 1;
const member1 = randomID();
const score2 = 2;
const member2 = randomID();

await new ZAddCommand([key1, { score: score1, member: member1 }]).exec(
client,
);
await new ZAddCommand([key2, { score: score2, member: member2 }]).exec(
client,
);

const res = await new ZUnionCommand([2, [key1, key2]])
.exec(
client,
);

assertEquals(res.length, 2);
assertEquals(res?.sort(), [member1, member2].sort());
});
});

Deno.test("with weights", async (t) => {
await t.step("returns the set", async () => {
const key1 = newKey();
const key2 = newKey();
const score1 = 1;
const member1 = randomID();
const score2 = 2;
const member2 = randomID();

await new ZAddCommand([key1, { score: score1, member: member1 }]).exec(
client,
);

await new ZAddCommand([key2, { score: score2, member: member2 }]).exec(
client,
);

const res = await new ZUnionCommand([2, [key1, key2], {
weights: [2, 3],
}]).exec(client);

assertEquals(res.length, 2);
});
});

Deno.test("aggregate", async (t) => {
await t.step("sum", async (t) => {
await t.step("returns the set", async () => {
const key1 = newKey();
const key2 = newKey();
const score1 = 1;
const member1 = randomID();
const score2 = 2;
const member2 = randomID();

await new ZAddCommand([key1, { score: score1, member: member1 }]).exec(
client,
);
await new ZAddCommand([key2, { score: score2, member: member2 }]).exec(
client,
);

const res = await new ZUnionCommand([2, [key1, key2], {
aggregate: "sum",
}]).exec(client);

assertEquals(Array.isArray(res), true);
assertEquals(res.length, 2);
});
});
await t.step("min", async (t) => {
await t.step("returns the set ", async () => {
const key1 = newKey();
const key2 = newKey();
const score1 = 1;
const member1 = randomID();
const score2 = 2;
const member2 = randomID();

await new ZAddCommand([key1, { score: score1, member: member1 }]).exec(
client,
);
await new ZAddCommand([key2, { score: score2, member: member2 }]).exec(
client,
);

const res = await new ZUnionCommand([2, [key1, key2], {
aggregate: "min",
}]).exec(client);
assertEquals(res.length, 2);
});
});
await t.step("max", async (t) => {
await t.step("returns the set ", async () => {
const key1 = newKey();
const key2 = newKey();
const score1 = 1;
const member1 = randomID();
const score2 = 2;
const member2 = randomID();

await new ZAddCommand([key1, { score: score1, member: member1 }]).exec(
client,
);
await new ZAddCommand([key2, { score: score2, member: member2 }]).exec(
client,
);

const res = await new ZUnionCommand([2, [key1, key2], {
aggregate: "max",
}]).exec(client);
assertEquals(res.length, 2);
});
});
});

Deno.test("withscores", async (t) => {
await t.step("returns the set", async () => {
const key1 = newKey();
const score1 = 1;
const member1 = randomID();

const key2 = newKey();
const member2 = randomID();
const score2 = 5;

await new ZAddCommand([key1, { score: score1, member: member1 }]).exec(
client,
);

await new ZAddCommand([key2, { score: score2, member: member2 }]).exec(
client,
);

const res = await new ZUnionCommand([2, [key1, key2], {
withScores: true,
}]).exec(client);

assertEquals(res.length, 4);
assertEquals(res[0], member1);
assertEquals(res[1], score1);
});
});
64 changes: 64 additions & 0 deletions pkg/commands/zunion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Command, CommandOptions } from "./command.ts";

export type ZUnionCommandOptions =
& {
withScores?: boolean;
aggregate?: "sum" | "min" | "max";
}
& (
| { weight: number; weights?: never }
| { weight?: never; weights: number[] }
| { weight?: never; weights?: never }
);

/**
* @see https://redis.io/commands/zunion
*/
export class ZUnionCommand<TData extends unknown[]>
extends Command<string[], TData> {
constructor(
cmd: [
numKeys: 1,
key: string,
opts?: ZUnionCommandOptions,
],
cmdOpts?: CommandOptions<string[], TData>,
);
constructor(
cmd: [
numKeys: number,
keys: string[],
opts?: ZUnionCommandOptions,
],
cmdOpts?: CommandOptions<string[], TData>,
);
constructor(
[numKeys, keyOrKeys, opts]: [
numKeys: number,
keyOrKeys: string | string[],
opts?: ZUnionCommandOptions,
],
cmdOpts?: CommandOptions<string[], TData>,
) {
const command: unknown[] = ["zunion", numKeys];
if (Array.isArray(keyOrKeys)) {
command.push(...keyOrKeys);
} else {
command.push(keyOrKeys);
}
if (opts) {
if ("weights" in opts && opts.weights) {
command.push("weights", ...opts.weights);
} else if ("weight" in opts && typeof opts.weight === "number") {
command.push("weights", opts.weight);
}
if ("aggregate" in opts) {
command.push("aggregate", opts.aggregate);
}
if (opts?.withScores) {
command.push("withscores");
}
}
super(command, cmdOpts);
}
}
3 changes: 2 additions & 1 deletion pkg/pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,10 @@ Deno.test("use all the things", async (t) => {
.zscan(newKey(), 0)
.zscore(newKey(), "member")
.zunionstore(newKey(), 1, [newKey()])
.zunion(1, [newKey()])
.json.set(newKey(), "$", { hello: "world" });

const res = await p.exec();
assertEquals(res.length, 120);
assertEquals(res.length, 121);
});
});
Loading

0 comments on commit 6460921

Please sign in to comment.