Skip to content

Commit 90c43fd

Browse files
authored
Node: add routing option to exec command
1 parent 8056ce0 commit 90c43fd

File tree

2 files changed

+66
-7
lines changed

2 files changed

+66
-7
lines changed

node/src/RedisClusterClient.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
createPing,
1414
} from "./Commands";
1515
import { connection_request, redis_request } from "./ProtobufMessage";
16-
import { ClusterTransaction } from "./Transaction";
16+
import { BaseTransaction, ClusterTransaction } from "./Transaction";
1717

1818
export type ClusterClientConfiguration = BaseClientConfiguration;
1919

@@ -48,16 +48,19 @@ export type SlotKeyTypes = {
4848
key: string;
4949
};
5050

51-
export type Routes =
51+
export type Routes =
52+
| SingleNodeRoute
5253
/**
5354
* Route request to all primary nodes.
5455
*/
55-
5656
| "allPrimaries"
5757
/**
5858
* Route request to all nodes.
5959
*/
60-
| "allNodes"
60+
| "allNodes";
61+
62+
63+
export type SingleNodeRoute =
6164
/**
6265
* Route request to a random node.
6366
*/
@@ -193,12 +196,15 @@ export class RedisClusterClient extends BaseClient {
193196
* See https://redis.io/topics/Transactions/ for details on Redis Transactions.
194197
*
195198
* @param transaction - A ClusterTransaction object containing a list of commands to be executed.
199+
* @param route - If `route` is not provided, the transaction will be routed to the slot owner of the first key found in the transaction.
200+
* If no key is found, the command will be sent to a random node.
201+
* If `route` is provided, the client will route the command to the nodes defined by `route`.
196202
* @returns A list of results corresponding to the execution of each command in the transaction.
197203
* If a command returns a value, it will be included in the list. If a command doesn't return a value,
198204
* the list entry will be null.
199205
*/
200-
public exec(transaction: ClusterTransaction): Promise<ReturnType[]> {
201-
return this.createWritePromise(transaction.commands);
206+
public exec(transaction: ClusterTransaction | BaseTransaction , route?: SingleNodeRoute): Promise<ReturnType[]> {
207+
return this.createWritePromise(transaction.commands , toProtobufRoute(route));
202208
}
203209

204210
/** Ping the Redis server.

node/tests/RedisClientInternals.test.ts

+54-1
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,21 @@ import { Reader } from "protobufjs";
88
import {
99
BaseClientConfiguration,
1010
ClosingError,
11+
InfoOptions,
1112
Logger,
1213
RedisClient,
1314
RedisClusterClient,
1415
RequestError,
1516
TimeoutError,
17+
Transaction
1618
} from "../build-ts";
1719
import { RedisClientConfiguration } from "../build-ts/src/RedisClient";
1820
import {
1921
connection_request,
2022
redis_request,
2123
response,
2224
} from "../src/ProtobufMessage";
23-
import { ClusterClientConfiguration } from "../src/RedisClusterClient";
25+
import { ClusterClientConfiguration, SlotKeyTypes } from "../src/RedisClusterClient";
2426

2527
const { RequestType, RedisRequest } = redis_request;
2628

@@ -247,6 +249,57 @@ describe("SocketConnectionInternals", () => {
247249
});
248250
});
249251

252+
it("should pass transaction with SlotKeyType", async () => {
253+
await testWithClusterResources(async (connection, socket) => {
254+
socket.once("data", (data) => {
255+
const reader = Reader.create(data);
256+
const request = RedisRequest.decodeDelimited(reader);
257+
258+
expect(request.transaction?.commands?.at(0)?.requestType).toEqual(
259+
RequestType.SetString
260+
);
261+
expect(request.transaction?.commands?.at(0)?.argsArray?.args?.length).toEqual(
262+
2
263+
);
264+
expect(request.route?.slotKeyRoute?.slotKey).toEqual("key");
265+
expect(request.route?.slotKeyRoute?.slotType).toEqual(0); // Primary = 0
266+
267+
sendResponse(socket, ResponseType.OK, request.callbackIdx);
268+
});
269+
const transaction = new Transaction();
270+
transaction.set("key" , "value");
271+
const slotKey: SlotKeyTypes = {
272+
type: "primarySlotKey",
273+
key: "key"
274+
};
275+
const result = await connection.exec(transaction, slotKey);
276+
expect(result).toBe("OK");
277+
});
278+
});
279+
280+
it("should pass transaction with random node", async () => {
281+
await testWithClusterResources(async (connection, socket) => {
282+
socket.once("data", (data) => {
283+
const reader = Reader.create(data);
284+
const request = RedisRequest.decodeDelimited(reader);
285+
286+
expect(request.transaction?.commands?.at(0)?.requestType).toEqual(
287+
RequestType.Info
288+
);
289+
expect(request.transaction?.commands?.at(0)?.argsArray?.args?.length).toEqual(
290+
1
291+
);
292+
expect(request.route?.simpleRoutes).toEqual(redis_request.SimpleRoutes.Random);
293+
294+
sendResponse(socket, ResponseType.Value, request.callbackIdx , "# Server");
295+
});
296+
const transaction = new Transaction();
297+
transaction.info([InfoOptions.Server]);
298+
const result = await connection.exec(transaction, "randomNode");
299+
expect(result).toEqual(expect.stringContaining("# Server"));
300+
});
301+
});
302+
250303
it("should pass OK returned from socket", async () => {
251304
await testWithResources(async (connection, socket) => {
252305
socket.once("data", (data) => {

0 commit comments

Comments
 (0)