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

feat: add bitfield command #1159

Merged
merged 6 commits into from
Jul 3, 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
51 changes: 51 additions & 0 deletions pkg/commands/bitfield.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { afterAll, describe, expect, test } from "bun:test";
import { keygen, newHttpClient } from "../test-utils";
import { BitFieldCommand } from "./bitfield";

const client = newHttpClient();

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

describe("when key is not set", () => {
test("returns 0", async () => {
const key = newKey();
const res = await new BitFieldCommand([key], client).get("u4", "#0").exec();
expect(res).toEqual([0]);
});
});

describe("when key is set", () => {
test("sets / gets value", async () => {
const key = newKey();
const value = 42;
const res = await new BitFieldCommand([key], client)
.set("u8", "#0", value)
.get("u8", "#0")
.exec();
expect(res).toEqual([0, value]);
});

test("increments value", async () => {
const key = newKey();
const value = 42;
const increment = 10;
const res = await new BitFieldCommand([key], client)
.set("u8", "#0", value)
.incrby("u8", "#0", increment)
.exec();
expect(res).toEqual([0, value + increment]);
});

test("overflows", async () => {
const key = newKey();
const value = 255;
const bitWidth = 8;
const res = await new BitFieldCommand([key], client)
.set(`u${bitWidth}`, "#0", value)
.incrby(`u${bitWidth}`, "#0", 10)
.overflow("WRAP")
.exec();
expect(res).toEqual([0, (value + 10) % 2 ** bitWidth]);
});
});
51 changes: 51 additions & 0 deletions pkg/commands/bitfield.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { type Requester } from "../http";
import { Command, type CommandOptions } from "./command";

type SubCommandArgs<TRest extends unknown[] = []> = [
encoding: string, // u1 - u63 | i1 - i64
offset: number | string, // <int> | #<int>
...TRest,
];

/**
* @see https://redis.io/commands/bitfield
*/
export class BitFieldCommand<T = Promise<number[]>> {
private command: (string | number)[];

constructor(
args: [key: string],
private client: Requester,
private opts?: CommandOptions<number[], number[]>,
private execOperation = (command: Command<number[], number[]>) =>
command.exec(this.client) as T,
) {
this.command = ["bitfield", ...args];
}

private chain(...args: typeof this.command) {
this.command.push(...args);
return this;
}

get(...args: SubCommandArgs) {
return this.chain("get", ...args);
}

set(...args: SubCommandArgs<[value: number]>) {
return this.chain("set", ...args);
}

incrby(...args: SubCommandArgs<[increment: number]>) {
return this.chain("incrby", ...args);
}

overflow(overflow: "WRAP" | "SAT" | "FAIL") {
return this.chain("overflow", overflow);
}

exec() {
const command = new Command(this.command, this.opts);
return this.execOperation(command);
}
}
3 changes: 2 additions & 1 deletion pkg/commands/mod.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./append";
export * from "./bitcount";
export * from "./bitfield";
export * from "./bitop";
export * from "./bitpos";
export * from "./command";
Expand Down Expand Up @@ -72,8 +73,8 @@ export * from "./lindex";
export * from "./linsert";
export * from "./llen";
export * from "./lmove";
export * from "./lpop";
export * from "./lmpop";
export * from "./lpop";
export * from "./lpos";
export * from "./lpush";
export * from "./lpushx";
Expand Down
4 changes: 3 additions & 1 deletion pkg/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export class UpstashError extends Error {

export class UrlError extends Error {
constructor(url: string) {
super(`Upstash Redis client was passed an invalid URL. You should pass the URL together with https. Received: "${url}". `);
super(
`Upstash Redis client was passed an invalid URL. You should pass the URL together with https. Received: "${url}". `,
);
this.name = "UrlError";
}
}
8 changes: 7 additions & 1 deletion pkg/pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ describe("use all the things", () => {

p.append(newKey(), "hello")
.bitcount(newKey(), 0, 1)
.bitfield(newKey())
.set("u4", "#0", 15)
.get("u4", "#0")
.overflow("WRAP")
.incrby("u4", "#0", 10)
.exec()
.bitop("and", newKey(), newKey())
.bitpos(newKey(), 1, 0)
.dbsize()
Expand Down Expand Up @@ -243,6 +249,6 @@ describe("use all the things", () => {
.json.set(newKey(), "$", { hello: "world" });

const res = await p.exec();
expect(res.length).toEqual(120);
expect(res.length).toEqual(121);
});
});
20 changes: 20 additions & 0 deletions pkg/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { HRandFieldCommand } from "./commands/hrandfield";
import {
AppendCommand,
BitCountCommand,
BitFieldCommand,
BitOpCommand,
BitPosCommand,
CopyCommand,
Expand Down Expand Up @@ -322,6 +323,25 @@ export class Pipeline<TCommands extends Command<any, any>[] = []> {
bitcount = (...args: CommandArgs<typeof BitCountCommand>) =>
this.chain(new BitCountCommand(args, this.commandOptions));

/**
* Returns an instance that can be used to execute `BITFIELD` commands on one key.
*
* @example
* ```typescript
* redis.set("mykey", 0);
* const result = await redis.pipeline()
* .bitfield("mykey")
* .set("u4", 0, 16)
* .incr("u4", "#1", 1)
* .exec();
* console.log(result); // [[0, 1]]
* ```
*
* @see https://redis.io/commands/bitfield
*/
bitfield = (...args: CommandArgs<typeof BitFieldCommand>) =>
new BitFieldCommand(args, this.client, this.commandOptions, this.chain.bind(this));

/**
* @see https://redis.io/commands/bitop
*/
Expand Down
19 changes: 19 additions & 0 deletions pkg/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createAutoPipelineProxy } from "../pkg/auto-pipeline";
import {
AppendCommand,
BitCountCommand,
BitFieldCommand,
lewxdev marked this conversation as resolved.
Show resolved Hide resolved
BitOpCommand,
BitPosCommand,
CommandOptions,
Expand Down Expand Up @@ -402,6 +403,24 @@ export class Redis {
multiExec: true,
});

/**
* Returns an instance that can be used to execute `BITFIELD` commands on one key.
*
* @example
* ```typescript
* redis.set("mykey", 0);
* const result = await redis.bitfield("mykey")
* .set("u4", 0, 16)
* .incr("u4", "#1", 1)
* .exec();
* console.log(result); // [0, 1]
* ```
*
* @see https://redis.io/commands/bitfield
*/
bitfield = (...args: CommandArgs<typeof BitFieldCommand>) =>
new BitFieldCommand(args, this.client, this.opts);

/**
* @see https://redis.io/commands/append
*/
Expand Down