From 605d5c1083e85abe5f0fa0ed9cf9a14875df41e4 Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Wed, 13 Mar 2024 13:18:50 +0200 Subject: [PATCH 1/9] implement parser conversion with firs GET command implemented --- packages/client/lib/RESP/types.ts | 2 + packages/client/lib/client/index.ts | 117 ++++++++--- packages/client/lib/client/multi-command.ts | 60 +++++- packages/client/lib/client/parser.ts | 92 +++++++++ packages/client/lib/client/pool.ts | 93 ++++++--- packages/client/lib/cluster/index.ts | 210 ++++++++++++++------ packages/client/lib/commander.ts | 4 +- packages/client/lib/commands/GET.spec.ts | 4 +- packages/client/lib/commands/GET.ts | 8 +- packages/client/lib/sentinel/index.ts | 9 + packages/client/lib/sentinel/utils.ts | 114 +++++++---- packages/client/lib/test-utils.ts | 19 ++ 12 files changed, 562 insertions(+), 170 deletions(-) create mode 100644 packages/client/lib/client/parser.ts diff --git a/packages/client/lib/RESP/types.ts b/packages/client/lib/RESP/types.ts index 9f0e9217345..a1dd236151e 100644 --- a/packages/client/lib/RESP/types.ts +++ b/packages/client/lib/RESP/types.ts @@ -1,3 +1,4 @@ +import { CommandParser } from '../client/parser'; import { BlobError, SimpleError } from '../errors'; import { RedisScriptConfig, SHA1 } from '../lua-script'; import { RESP_TYPES } from './decoder'; @@ -272,6 +273,7 @@ export type Command = { */ IS_FORWARD_COMMAND?: boolean; // POLICIES?: CommandPolicies; + parseCommand?(this: void, parser: CommandParser, ...args: Array): void; transformArguments(this: void, ...args: Array): CommandArguments; TRANSFORM_LEGACY_REPLY?: boolean; transformReply: TransformReply | Record; diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 3efa793eeb9..2bb0a8423db 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -7,7 +7,7 @@ import { ClientClosedError, ClientOfflineError, DisconnectsClientError, WatchErr import { URL } from 'node:url'; import { TcpSocketConnectOpts } from 'node:net'; import { PUBSUB_TYPE, PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub'; -import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply } from '../RESP/types'; +import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply, TransformReply } from '../RESP/types'; import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command'; import { RedisMultiQueuedCommand } from '../multi-command'; import HELLO, { HelloOptions } from '../commands/HELLO'; @@ -15,6 +15,7 @@ import { ScanOptions, ScanCommonOptions } from '../commands/SCAN'; import { RedisLegacyClient, RedisLegacyClientType } from './legacy-mode'; import { RedisPoolOptions, RedisClientPool } from './pool'; import { RedisVariadicArgument, pushVariadicArguments } from '../commands/generic-transformers'; +import { BasicCommandParser, CommandParser } from './parser'; export interface RedisClientOptions< M extends RedisModules = RedisModules, @@ -151,52 +152,84 @@ export default class RedisClient< > extends EventEmitter { static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: ProxyClient, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this.sendCommand(redisArgs, this._commandOptions); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; - }; + if (command.parseCommand) { + const parser = this._self.#newCommandParser(resp); + command.parseCommand(parser, ...args); + + return this.executeCommand(undefined, parser, this._commandOptions, transformReply); + } else { + const redisArgs = command.transformArguments(...args), + reply = await this.sendCommand(redisArgs, this._commandOptions); + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + }; + } } static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: NamespaceProxyClient, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + if (command.parseCommand) { + const parser = this._self._self.#newCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self.executeCommand(undefined, parser, this._self._commandOptions, transformReply); + } else { + const redisArgs = command.transformArguments(...args), + reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return async function (this: NamespaceProxyClient, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - reply = await this._self.sendCommand( - prefix.concat(fnArgs), - this._self._commandOptions - ); - return transformReply ? - transformReply(reply, fnArgs.preserve) : - reply; + if (fn.parseCommand) { + const parser = this._self._self.#newCommandParser(resp); + fn.parseCommand(parser, ...args); + + return this._self.executeCommand(prefix, parser, this._self._commandOptions, transformReply); + } else { + const fnArgs = fn.transformArguments(...args), + reply = await this._self.sendCommand( + prefix.concat(fnArgs), + this._self._commandOptions + ); + return transformReply ? + transformReply(reply, fnArgs.preserve) : + reply; + } }; } static #createScriptCommand(script: RedisScript, resp: RespVersions) { - const prefix = scriptArgumentsPrefix(script), - transformReply = getTransformReply(script, resp); + const prefix = scriptArgumentsPrefix(script); + const transformReply = getTransformReply(script, resp); + return async function (this: ProxyClient, ...args: Array) { - const scriptArgs = script.transformArguments(...args), - redisArgs = prefix.concat(scriptArgs), - reply = await this.executeScript(script, redisArgs, this._commandOptions); - return transformReply ? - transformReply(reply, scriptArgs.preserve) : - reply; - }; + if (script.parseCommand) { + const parser = this._self.#newCommandParser(resp); + script.parseCommand(parser, ...args); + + return this.executeCommand(prefix, parser, this._commandOptions, transformReply); + } else { + const scriptArgs = script.transformArguments(...args), + redisArgs = prefix.concat(scriptArgs), + reply = await this.executeScript(script, redisArgs, this._commandOptions); + return transformReply ? + transformReply(reply, scriptArgs.preserve) : + reply; + }; + } } static factory< @@ -309,6 +342,10 @@ export default class RedisClient< this._self.#dirtyWatch = msg; } + #newCommandParser(resp: RespVersions): CommandParser { + return new BasicCommandParser(resp); + } + constructor(options?: RedisClientOptions) { super(); this.#options = this.#initiateOptions(options); @@ -573,6 +610,24 @@ export default class RedisClient< return this as unknown as RedisClientType; } + async executeCommand( + prefix: Array | undefined, + parser: CommandParser, + commandOptions: CommandOptions | undefined, + transformReply: TransformReply | undefined + ) { + const redisArgs = prefix ? prefix.concat(parser.redisArgs) : parser.redisArgs; + const fn = () => { return this.sendCommand(redisArgs, commandOptions) }; + + const reply = await fn(); + + if (transformReply) { + return transformReply(reply, parser.preserve); + } + + return reply; + } + sendCommand( args: Array, options?: CommandOptions diff --git a/packages/client/lib/client/multi-command.ts b/packages/client/lib/client/multi-command.ts index ef65144d56b..d024c43fe5d 100644 --- a/packages/client/lib/client/multi-command.ts +++ b/packages/client/lib/client/multi-command.ts @@ -2,6 +2,7 @@ import COMMANDS from '../commands'; import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType, RedisMultiQueuedCommand } from '../multi-command'; import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping } from '../RESP/types'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; +import { BasicCommandParser } from './parser'; type CommandSignature< REPLIES extends Array, @@ -88,9 +89,20 @@ type ExecuteMulti = (commands: Array, selectedDB?: numb export default class RedisClientMultiCommand { static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: RedisClientMultiCommand, ...args: Array) { + let redisArgs: CommandArguments; + + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + } else { + redisArgs = command.transformArguments(...args); + } + return this.addCommand( - command.transformArguments(...args), + redisArgs, transformReply ); }; @@ -98,21 +110,43 @@ export default class RedisClientMultiCommand { static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: { _self: RedisClientMultiCommand }, ...args: Array) { + let redisArgs: CommandArguments; + + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + } else { + redisArgs = command.transformArguments(...args); + } + return this._self.addCommand( - command.transformArguments(...args), + redisArgs, transformReply ); }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return function (this: { _self: RedisClientMultiCommand }, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - redisArgs: CommandArguments = prefix.concat(fnArgs); + let fnArgs: CommandArguments; + + if (fn.parseCommand) { + const parser = new BasicCommandParser(resp); + fn.parseCommand(parser, ...args); + fnArgs = parser.redisArgs; + } else { + fnArgs = fn.transformArguments(...args); + } + + const redisArgs: CommandArguments = prefix.concat(fnArgs); redisArgs.preserve = fnArgs.preserve; + return this._self.addCommand( redisArgs, transformReply @@ -122,12 +156,24 @@ export default class RedisClientMultiCommand { static #createScriptCommand(script: RedisScript, resp: RespVersions) { const transformReply = getTransformReply(script, resp); + return function (this: RedisClientMultiCommand, ...args: Array) { + let redisArgs: CommandArguments; + + if (script.parseCommand) { + const parser = new BasicCommandParser(resp); + script.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + } else { + redisArgs = script.transformArguments(...args); + } + this.#multi.addScript( script, - script.transformArguments(...args), + redisArgs, transformReply ); + return this; }; } diff --git a/packages/client/lib/client/parser.ts b/packages/client/lib/client/parser.ts new file mode 100644 index 00000000000..e462f542b65 --- /dev/null +++ b/packages/client/lib/client/parser.ts @@ -0,0 +1,92 @@ +import { RedisArgument, RespVersions } from "../.."; +import { RedisVariadicArgument } from "../commands/generic-transformers"; + +export interface CommandParser { + redisArgs: Array; + respVersion: RespVersions; + preserve: unknown; + + push: (arg: RedisArgument) => unknown; + pushVariadic: (vals: RedisVariadicArgument) => unknown; + pushKey: (key: RedisArgument) => unknown; // normal push of keys + pushKeys: (keys: RedisVariadicArgument) => unknown; // push multiple keys at a time + setCachable: () => unknown; + setPreserve: (val: unknown) => unknown; +} + +export abstract class AbstractCommandParser implements CommandParser { + #redisArgs: Array = []; + #respVersion: RespVersions; + #preserve: unknown; + + constructor(respVersion: RespVersions = 2) { + this.#respVersion = respVersion; + } + + get redisArgs() { + return this.#redisArgs; + } + + get respVersion() { + return this.#respVersion; + } + + get preserve() { + return this.#preserve; + } + + push(arg: RedisArgument) { + this.#redisArgs.push(arg); + + }; + + pushVariadic(vals: RedisVariadicArgument) { + if (Array.isArray(vals)) { + for (const val of vals) { + this.push(val); + } + } else { + this.push(vals); + } + } + + pushKey(key: RedisArgument) { + this.#redisArgs.push(key); + }; + + pushKeys(keys: RedisVariadicArgument) { + if (Array.isArray(keys)) { + this.#redisArgs.push(...keys); + } else { + this.#redisArgs.push(keys); + } + } + + setPreserve(val: unknown) { + this.#preserve = val; + } + + setCachable() {}; +} + +/* Note: I do it this way, where BasicCommandParser extends Abstract without any changes, + and CachedCommandParser extends Abstract with changes, to enable them to be easily + distinguishable at runtime. If Cached extended Basic, then Cached would also be a Basic, + thereby making them harder to distinguish. +*/ +export class BasicCommandParser extends AbstractCommandParser {}; + +export interface ClusterCommandParser extends CommandParser { + firstKey: RedisArgument | undefined; +} + +export class BasicClusterCommandParser extends BasicCommandParser implements ClusterCommandParser { + firstKey: RedisArgument | undefined; + + override pushKey(key: RedisArgument): void { + if (!this.firstKey) { + this.firstKey = key; + } + super.pushKey(key); + } +} \ No newline at end of file diff --git a/packages/client/lib/client/pool.ts b/packages/client/lib/client/pool.ts index fc996e07625..2167f0dd011 100644 --- a/packages/client/lib/client/pool.ts +++ b/packages/client/lib/client/pool.ts @@ -7,6 +7,7 @@ import { TimeoutError } from '../errors'; import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander'; import { CommandOptions } from './commands-queue'; import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command'; +import { CommandParser, BasicCommandParser } from './parser'; export interface RedisPoolOptions { /** @@ -60,51 +61,83 @@ export class RedisClientPool< > extends EventEmitter { static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: ProxyPool, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this.sendCommand(redisArgs, this._commandOptions); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + if (command.parseCommand) { + const parser = this._self.#newCommandParser(resp); + command.parseCommand(parser, ...args); + + return this.execute(client => client.executeCommand(undefined, parser, this._commandOptions, transformReply)) + } else { + const redisArgs = command.transformArguments(...args), + reply = await this.sendCommand(redisArgs, this._commandOptions); + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: NamespaceProxyPool, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + if (command.parseCommand) { + const parser = this._self._self.#newCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self.execute(client => client.executeCommand(undefined, parser, this._self._commandOptions, transformReply)) + } else { + const redisArgs = command.transformArguments(...args), + reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return async function (this: NamespaceProxyPool, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - reply = await this._self.sendCommand( - prefix.concat(fnArgs), - this._self._commandOptions - ); - return transformReply ? - transformReply(reply, fnArgs.preserve) : - reply; + if (fn.parseCommand) { + const parser = this._self.#newCommandParser(resp); + fn.parseCommand(parser, ...args); + + return this._self.execute(client => client.executeCommand(prefix, parser, this._self._commandOptions, transformReply)) + } else { + const fnArgs = fn.transformArguments(...args), + reply = await this._self.sendCommand( + prefix.concat(fnArgs), + this._self._commandOptions + ); + return transformReply ? + transformReply(reply, fnArgs.preserve) : + reply; + } }; } static #createScriptCommand(script: RedisScript, resp: RespVersions) { - const prefix = scriptArgumentsPrefix(script), - transformReply = getTransformReply(script, resp); + const prefix = scriptArgumentsPrefix(script); + const transformReply = getTransformReply(script, resp); + return async function (this: ProxyPool, ...args: Array) { - const scriptArgs = script.transformArguments(...args), - redisArgs = prefix.concat(scriptArgs), - reply = await this.executeScript(script, redisArgs, this._commandOptions); - return transformReply ? - transformReply(reply, scriptArgs.preserve) : - reply; + if (script.parseCommand) { + const parser = this._self.#newCommandParser(resp); + script.parseCommand(parser, ...args); + + return this.execute(client => client.executeCommand(prefix, parser, this._commandOptions, transformReply)) + } else { + const scriptArgs = script.transformArguments(...args), + redisArgs = prefix.concat(scriptArgs), + reply = await this.executeScript(script, redisArgs, this._commandOptions); + return transformReply ? + transformReply(reply, scriptArgs.preserve) : + reply; + } }; } @@ -207,6 +240,10 @@ export class RedisClientPool< return this._self.#isClosing; } + #newCommandParser(resp: RespVersions): CommandParser { + return new BasicCommandParser(resp); + } + /** * You are probably looking for {@link RedisClient.createPool `RedisClient.createPool`}, * {@link RedisClientPool.fromClient `RedisClientPool.fromClient`}, diff --git a/packages/client/lib/cluster/index.ts b/packages/client/lib/cluster/index.ts index d6018fc270e..9a1403e4f05 100644 --- a/packages/client/lib/cluster/index.ts +++ b/packages/client/lib/cluster/index.ts @@ -9,6 +9,8 @@ import RedisClusterMultiCommand, { RedisClusterMultiCommandType } from './multi- import { PubSubListener } from '../client/pub-sub'; import { ErrorReply } from '../errors'; import { RedisTcpSocketOptions } from '../client/socket'; +import ASKING from '../commands/ASKING'; +import { BasicClusterCommandParser, ClusterCommandParser } from '../client/parser'; interface ClusterCommander< M extends RedisModules, @@ -169,14 +171,27 @@ export default class RedisCluster< static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: ProxyCluster, ...args: Array) { - const redisArgs = command.transformArguments(...args), - firstKey = RedisCluster.extractFirstKey( + if (command.parseCommand) { + const parser = this._self.#newCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self.#execute( + parser.firstKey, + command.IS_READ_ONLY, + this._commandOptions, + (client, opts) => client.executeCommand(undefined, parser, opts, transformReply) + ); + } else { + const redisArgs = command.transformArguments(...args); + const firstKey = RedisCluster.extractFirstKey( command, args, redisArgs - ), - reply = await this.sendCommand( + ); + + const reply = await this.sendCommand( firstKey, command.IS_READ_ONLY, redisArgs, @@ -184,83 +199,123 @@ export default class RedisCluster< // command.POLICIES ); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: NamespaceProxyCluster, ...args: Array) { - const redisArgs = command.transformArguments(...args), - firstKey = RedisCluster.extractFirstKey( - command, - args, - redisArgs - ), - reply = await this._self.sendCommand( - firstKey, + if (command.parseCommand) { + const parser = this._self._self.#newCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self.#execute( + parser.firstKey, command.IS_READ_ONLY, - redisArgs, this._self._commandOptions, - // command.POLICIES + (client, opts) => client.executeCommand(undefined, parser, opts, transformReply) ); - - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + } else { + const redisArgs = command.transformArguments(...args); + const firstKey = RedisCluster.extractFirstKey( + command, + args, + redisArgs + ), + reply = await this._self.sendCommand( + firstKey, + command.IS_READ_ONLY, + redisArgs, + this._self._commandOptions, + // command.POLICIES + ); + + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return async function (this: NamespaceProxyCluster, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - firstKey = RedisCluster.extractFirstKey( - fn, - args, - fnArgs - ), - redisArgs = prefix.concat(fnArgs), - reply = await this._self.sendCommand( - firstKey, + if (fn.parseCommand) { + const parser = this._self._self.#newCommandParser(resp); + fn.parseCommand(parser, ...args); + + return this._self.#execute( + parser.firstKey, fn.IS_READ_ONLY, - redisArgs, this._self._commandOptions, - // fn.POLICIES + (client, opts) => client.executeCommand(prefix, parser, opts, transformReply) ); - - return transformReply ? - transformReply(reply, fnArgs.preserve) : - reply; + } else { + const fnArgs = fn.transformArguments(...args); + const firstKey = RedisCluster.extractFirstKey( + fn, + args, + fnArgs + ), + redisArgs = prefix.concat(fnArgs), + reply = await this._self.sendCommand( + firstKey, + fn.IS_READ_ONLY, + redisArgs, + this._self._commandOptions, + // fn.POLICIES + ); + + return transformReply ? + transformReply(reply, fnArgs.preserve) : + reply; + } }; } static #createScriptCommand(script: RedisScript, resp: RespVersions) { - const prefix = scriptArgumentsPrefix(script), - transformReply = getTransformReply(script, resp); + const prefix = scriptArgumentsPrefix(script); + const transformReply = getTransformReply(script, resp); + return async function (this: ProxyCluster, ...args: Array) { - const scriptArgs = script.transformArguments(...args), - firstKey = RedisCluster.extractFirstKey( - script, - args, - scriptArgs - ), - redisArgs = prefix.concat(scriptArgs), - reply = await this.executeScript( - script, - firstKey, + if (script.parseCommand) { + const parser = this._self.#newCommandParser(resp); + script.parseCommand(parser, ...args); + + return this._self.#execute( + parser.firstKey, script.IS_READ_ONLY, - redisArgs, this._commandOptions, - // script.POLICIES + (client, opts) => client.executeCommand(prefix, parser, opts, transformReply) ); - - return transformReply ? - transformReply(reply, scriptArgs.preserve) : - reply; + } else { + const scriptArgs = script.transformArguments(...args), + firstKey = RedisCluster.extractFirstKey( + script, + args, + scriptArgs + ), + redisArgs = prefix.concat(scriptArgs), + reply = await this.executeScript( + script, + firstKey, + script.IS_READ_ONLY, + redisArgs, + this._commandOptions, + // script.POLICIES + ); + + return transformReply ? + transformReply(reply, scriptArgs.preserve) : + reply; + } }; } @@ -351,6 +406,10 @@ export default class RedisCluster< return this._self.#slots.isOpen; } + #newCommandParser(resp: RespVersions): ClusterCommandParser { + return new BasicClusterCommandParser(resp); + } + constructor(options: RedisClusterOptions) { super(); @@ -433,18 +492,41 @@ export default class RedisCluster< // return this._commandOptionsProxy('policies', policies); // } + #handleAsk( + fn: (client: RedisClientType, opts?: ClusterCommandOptions) => Promise + ) { + return async (client: RedisClientType, options?: ClusterCommandOptions) => { + const chainId = Symbol("asking chain"); + const opts = options ? {...options} : {}; + opts.chainId = chainId; + + const ret = await Promise.all( + [ + client.sendCommand(ASKING.transformArguments(), {chainId: chainId}), + fn(client, opts) + ] + ); + + return ret[1]; + }; + } + async #execute( firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, - fn: (client: RedisClientType) => Promise + options: ClusterCommandOptions | undefined, + fn: (client: RedisClientType, opts?: ClusterCommandOptions) => Promise ): Promise { const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16; - let client = await this.#slots.getClient(firstKey, isReadonly), - i = 0; + let client = await this.#slots.getClient(firstKey, isReadonly); + let i = 0; + let myFn = fn; + while (true) { try { - return await fn(client); + return await myFn(client, options); } catch (err) { + myFn = fn; // TODO: error class if (++i > maxCommandRedirections || !(err instanceof Error)) { throw err; @@ -462,7 +544,7 @@ export default class RedisCluster< throw new Error(`Cannot find node ${address}`); } - await redirectTo.asking(); + myFn = this.#handleAsk(fn); client = redirectTo; continue; } @@ -488,7 +570,8 @@ export default class RedisCluster< return this._self.#execute( firstKey, isReadonly, - client => client.sendCommand(args, options) + options, + (client, opts) => client.sendCommand(args, opts) ); } @@ -502,7 +585,8 @@ export default class RedisCluster< return this._self.#execute( firstKey, isReadonly, - client => client.executeScript(script, args, options) + options, + (client, opts) => client.executeScript(script, args, options) ); } diff --git a/packages/client/lib/commander.ts b/packages/client/lib/commander.ts index d96aaa7128e..b3b4204698f 100644 --- a/packages/client/lib/commander.ts +++ b/packages/client/lib/commander.ts @@ -1,4 +1,4 @@ -import { Command, CommanderConfig, RedisCommands, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, RespVersions } from './RESP/types'; +import { Command, CommanderConfig, RedisCommands, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, RespVersions, TransformReply } from './RESP/types'; interface AttachConfigOptions< M extends RedisModules, @@ -78,7 +78,7 @@ function attachNamespace(prototype: any, name: PropertyKey, fns: any) { }); } -export function getTransformReply(command: Command, resp: RespVersions) { +export function getTransformReply(command: Command, resp: RespVersions): TransformReply | undefined { switch (typeof command.transformReply) { case 'function': return command.transformReply; diff --git a/packages/client/lib/commands/GET.spec.ts b/packages/client/lib/commands/GET.spec.ts index 4bd74183222..f6a5ff3ef8f 100644 --- a/packages/client/lib/commands/GET.spec.ts +++ b/packages/client/lib/commands/GET.spec.ts @@ -1,11 +1,11 @@ import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL } from '../test-utils'; +import testUtils, { GLOBAL, parseArgs } from '../test-utils'; import GET from './GET'; describe('GET', () => { it('transformArguments', () => { assert.deepEqual( - GET.transformArguments('key'), + parseArgs(GET, 'key'), ['GET', 'key'] ); }); diff --git a/packages/client/lib/commands/GET.ts b/packages/client/lib/commands/GET.ts index bb3db4f76d9..bfd6ed6d39e 100644 --- a/packages/client/lib/commands/GET.ts +++ b/packages/client/lib/commands/GET.ts @@ -1,10 +1,14 @@ import { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types'; +import { CommandParser } from '../client/parser'; export default { FIRST_KEY_INDEX: 1, IS_READ_ONLY: true, - transformArguments(key: RedisArgument) { - return ['GET', key]; + parseCommand(parser: CommandParser, key: RedisArgument) { + parser.setCachable(); + parser.push('GET'); + parser.pushKey(key); }, + transformArguments: (key: RedisArgument) => { return [] }, transformReply: undefined as unknown as () => BlobStringReply | NullReply } as const satisfies Command; diff --git a/packages/client/lib/sentinel/index.ts b/packages/client/lib/sentinel/index.ts index 57819133e0c..d3c0465e60f 100644 --- a/packages/client/lib/sentinel/index.ts +++ b/packages/client/lib/sentinel/index.ts @@ -16,6 +16,7 @@ import { RedisVariadicArgument } from '../commands/generic-transformers'; import { WaitQueue } from './wait-queue'; import { TcpNetConnectOpts } from 'node:net'; import { RedisTcpSocketOptions } from '../client/socket'; +import { CommandParser, BasicCommandParser } from '../client/parser'; interface ClientInfo { id: number; @@ -46,6 +47,10 @@ export class RedisSentinelClient< #commandOptions?: CommandOptions; + newCommandParser(resp: RespVersions): CommandParser { + return new BasicCommandParser(resp); + } + constructor( internal: RedisSentinelInternal, clientInfo: ClientInfo, @@ -277,6 +282,10 @@ export default class RedisSentinel< #masterClientCount = 0; #masterClientInfo?: ClientInfo; + newCommandParser(resp: RespVersions): CommandParser { + return new BasicCommandParser(resp); + } + constructor(options: RedisSentinelOptions) { super(); diff --git a/packages/client/lib/sentinel/utils.ts b/packages/client/lib/sentinel/utils.ts index 4ae829183a2..2dc27669fc7 100644 --- a/packages/client/lib/sentinel/utils.ts +++ b/packages/client/lib/sentinel/utils.ts @@ -37,69 +37,113 @@ export function clientSocketToNode(socket: RedisSocketOptions): RedisNode { export function createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: T, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this._self.sendCommand( + if (command.parseCommand) { + const parser = this._self.newCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self._execute( command.IS_READ_ONLY, - redisArgs, - this._self.commandOptions + client => client.executeCommand(undefined, parser, this.commandOptions, transformReply) ); + } else { + const redisArgs = command.transformArguments(...args); + const reply = await this._self.sendCommand( + command.IS_READ_ONLY, + redisArgs, + this._self.commandOptions + ); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } export function createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return async function (this: T, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - redisArgs = prefix.concat(fnArgs), - reply = await this._self._self.sendCommand( + if (fn.parseCommand) { + const parser = this._self._self.newCommandParser(resp); + fn.parseCommand(parser, ...args); + + return this._self._execute( fn.IS_READ_ONLY, - redisArgs, - this._self._self.commandOptions + client => client.executeCommand(prefix, parser, this._self.commandOptions, transformReply) ); + } else { + const fnArgs = fn.transformArguments(...args), + redisArgs = prefix.concat(fnArgs), + reply = await this._self._self.sendCommand( + fn.IS_READ_ONLY, + redisArgs, + this._self._self.commandOptions + ); - return transformReply ? - transformReply(reply, fnArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, fnArgs.preserve) : + reply; + } } }; export function createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: T, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this._self._self.sendCommand( + if (command.parseCommand) { + const parser = this._self._self.newCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self._execute( command.IS_READ_ONLY, - redisArgs, - this._self._self.commandOptions + client => client.executeCommand(undefined, parser, this._self.commandOptions, transformReply) ); + } else { + const redisArgs = command.transformArguments(...args), + reply = await this._self._self.sendCommand( + command.IS_READ_ONLY, + redisArgs, + this._self._self.commandOptions + ); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } } }; export function createScriptCommand(script: RedisScript, resp: RespVersions) { - const prefix = scriptArgumentsPrefix(script), - transformReply = getTransformReply(script, resp); + const prefix = scriptArgumentsPrefix(script); + const transformReply = getTransformReply(script, resp); + return async function (this: T, ...args: Array) { - const scriptArgs = script.transformArguments(...args), - redisArgs = prefix.concat(scriptArgs), - reply = await this._self.executeScript( - script, + if (script.parseCommand) { + const parser = this._self.newCommandParser(resp); + script.parseCommand(parser, ...args); + + return this._self._execute( script.IS_READ_ONLY, - redisArgs, - this._self.commandOptions + client => client.executeCommand(prefix, parser, this.commandOptions, transformReply) ); + } else { + const scriptArgs = script.transformArguments(...args), + redisArgs = prefix.concat(scriptArgs), + reply = await this._self.executeScript( + script, + script.IS_READ_ONLY, + redisArgs, + this._self.commandOptions + ); - return transformReply ? - transformReply(reply, scriptArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, scriptArgs.preserve) : + reply; + } }; } diff --git a/packages/client/lib/test-utils.ts b/packages/client/lib/test-utils.ts index 81aac6f9b03..f1a2b6c5059 100644 --- a/packages/client/lib/test-utils.ts +++ b/packages/client/lib/test-utils.ts @@ -1,6 +1,8 @@ import TestUtils from '@redis/test-utils'; import { SinonSpy } from 'sinon'; import { setTimeout } from 'node:timers/promises'; +import { Command, CommandArguments } from './RESP/types'; +import { BasicCommandParser } from './client/parser'; const utils = new TestUtils({ dockerImageName: 'redis', @@ -67,3 +69,20 @@ export const BLOCKING_MIN_VALUE = ( utils.isVersionGreaterThan([6]) ? 0.01 : 1 ); + +export function parseArgs(command: Command, ...args: Array) { + const parser = new BasicCommandParser(); + command.parseCommand!(parser, ...args); + + const redisArgs: CommandArguments = parser.redisArgs; + if (parser.preserve) { + redisArgs.preserve = parser.preserve; + } + return redisArgs; +} + +export function parseArgsWith(command: Command, ...args: Array) { + const parser = new BasicCommandParser(); + command.parseCommand!(parser, ...args); + return parser.preserve; +} \ No newline at end of file From 14b6ad75427edf929197b124aebac1779046c6bc Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Wed, 13 Mar 2024 16:02:23 +0200 Subject: [PATCH 2/9] missed handling for multi in cluster --- packages/client/lib/cluster/multi-command.ts | 67 +++++++++++++++++--- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/packages/client/lib/cluster/multi-command.ts b/packages/client/lib/cluster/multi-command.ts index 225d1624653..2dc130d7371 100644 --- a/packages/client/lib/cluster/multi-command.ts +++ b/packages/client/lib/cluster/multi-command.ts @@ -3,6 +3,7 @@ import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType, RedisMultiQ import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping, RedisArgument } from '../RESP/types'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; import RedisCluster from '.'; +import { BasicClusterCommandParser } from '../client/parser'; type CommandSignature< REPLIES extends Array, @@ -93,13 +94,24 @@ export type ClusterMultiExecute = ( export default class RedisClusterMultiCommand { static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: RedisClusterMultiCommand, ...args: Array) { - const redisArgs = command.transformArguments(...args), + let redisArgs: CommandArguments = []; + let firstKey: RedisArgument | undefined; + + if (command.parseCommand) { + const parser = new BasicClusterCommandParser(resp); + command.parseCommand(parser, ...args); + firstKey = parser.firstKey; + } else { + redisArgs = command.transformArguments(...args); firstKey = RedisCluster.extractFirstKey( command, args, redisArgs ); + } + return this.addCommand( firstKey, command.IS_READ_ONLY, @@ -111,13 +123,23 @@ export default class RedisClusterMultiCommand { static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: { _self: RedisClusterMultiCommand }, ...args: Array) { - const redisArgs = command.transformArguments(...args), + let redisArgs: CommandArguments = []; + let firstKey: RedisArgument | undefined; + + if (command.parseCommand) { + const parser = new BasicClusterCommandParser(resp); + command.parseCommand(parser, ...args); + firstKey = parser.firstKey; + } else { + redisArgs = command.transformArguments(...args); firstKey = RedisCluster.extractFirstKey( command, args, redisArgs ); + } return this._self.addCommand( firstKey, command.IS_READ_ONLY, @@ -128,17 +150,29 @@ export default class RedisClusterMultiCommand { } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return function (this: { _self: RedisClusterMultiCommand }, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - redisArgs: CommandArguments = prefix.concat(fnArgs), + let fnArgs: CommandArguments = []; + let firstKey: RedisArgument | undefined; + + if (fn.parseCommand) { + const parser = new BasicClusterCommandParser(resp); + fn.parseCommand(parser, ...args); + firstKey = parser.firstKey; + } else { + fnArgs = fn.transformArguments(...args); firstKey = RedisCluster.extractFirstKey( fn, args, fnArgs ); + } + + const redisArgs: CommandArguments = prefix.concat(fnArgs); redisArgs.preserve = fnArgs.preserve; + return this._self.addCommand( firstKey, fn.IS_READ_ONLY, @@ -150,14 +184,26 @@ export default class RedisClusterMultiCommand { static #createScriptCommand(script: RedisScript, resp: RespVersions) { const transformReply = getTransformReply(script, resp); + return function (this: RedisClusterMultiCommand, ...args: Array) { - const scriptArgs = script.transformArguments(...args); - this.#setState( - RedisCluster.extractFirstKey( + let scriptArgs: CommandArguments = []; + let firstKey: RedisArgument | undefined; + + if (script.parseCommand) { + const parser = new BasicClusterCommandParser(resp); + script.parseCommand(parser, ...args); + firstKey = parser.firstKey; + } else { + scriptArgs = script.transformArguments(...args); + firstKey = RedisCluster.extractFirstKey( script, args, scriptArgs - ), + ) + } + + this.#setState( + firstKey, script.IS_READ_ONLY ); this.#multi.addScript( @@ -165,6 +211,7 @@ export default class RedisClusterMultiCommand { scriptArgs, transformReply ); + return this; }; } From 761542b713e135584c8ceba4c7e5db973cfb756a Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Wed, 13 Mar 2024 16:16:42 +0200 Subject: [PATCH 3/9] fix all multis for preserve --- packages/client/lib/client/multi-command.ts | 4 ++ packages/client/lib/cluster/multi-command.ts | 8 +++ .../client/lib/sentinel/multi-commands.ts | 58 +++++++++++++++++-- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/packages/client/lib/client/multi-command.ts b/packages/client/lib/client/multi-command.ts index d024c43fe5d..b02078157c7 100644 --- a/packages/client/lib/client/multi-command.ts +++ b/packages/client/lib/client/multi-command.ts @@ -97,6 +97,7 @@ export default class RedisClientMultiCommand { const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; } else { redisArgs = command.transformArguments(...args); } @@ -118,6 +119,7 @@ export default class RedisClientMultiCommand { const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; } else { redisArgs = command.transformArguments(...args); } @@ -140,6 +142,7 @@ export default class RedisClientMultiCommand { const parser = new BasicCommandParser(resp); fn.parseCommand(parser, ...args); fnArgs = parser.redisArgs; + fnArgs.preserve = parser.preserve; } else { fnArgs = fn.transformArguments(...args); } @@ -164,6 +167,7 @@ export default class RedisClientMultiCommand { const parser = new BasicCommandParser(resp); script.parseCommand(parser, ...args); redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; } else { redisArgs = script.transformArguments(...args); } diff --git a/packages/client/lib/cluster/multi-command.ts b/packages/client/lib/cluster/multi-command.ts index 2dc130d7371..851f217bf0d 100644 --- a/packages/client/lib/cluster/multi-command.ts +++ b/packages/client/lib/cluster/multi-command.ts @@ -102,6 +102,8 @@ export default class RedisClusterMultiCommand { if (command.parseCommand) { const parser = new BasicClusterCommandParser(resp); command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; firstKey = parser.firstKey; } else { redisArgs = command.transformArguments(...args); @@ -131,6 +133,8 @@ export default class RedisClusterMultiCommand { if (command.parseCommand) { const parser = new BasicClusterCommandParser(resp); command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; firstKey = parser.firstKey; } else { redisArgs = command.transformArguments(...args); @@ -160,6 +164,8 @@ export default class RedisClusterMultiCommand { if (fn.parseCommand) { const parser = new BasicClusterCommandParser(resp); fn.parseCommand(parser, ...args); + fnArgs = parser.redisArgs; + fnArgs.preserve = parser.preserve; firstKey = parser.firstKey; } else { fnArgs = fn.transformArguments(...args); @@ -192,6 +198,8 @@ export default class RedisClusterMultiCommand { if (script.parseCommand) { const parser = new BasicClusterCommandParser(resp); script.parseCommand(parser, ...args); + scriptArgs = parser.redisArgs; + scriptArgs.preserve = parser.preserve; firstKey = parser.firstKey; } else { scriptArgs = script.transformArguments(...args); diff --git a/packages/client/lib/sentinel/multi-commands.ts b/packages/client/lib/sentinel/multi-commands.ts index 12da9b2c97c..ebf856a5381 100644 --- a/packages/client/lib/sentinel/multi-commands.ts +++ b/packages/client/lib/sentinel/multi-commands.ts @@ -3,6 +3,7 @@ import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType } from '../m import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping } from '../RESP/types'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; import { RedisSentinelType } from './types'; +import { BasicCommandParser } from '../client/parser'; type CommandSignature< REPLIES extends Array, @@ -87,8 +88,18 @@ export type RedisSentinelMultiCommandType< export default class RedisSentinelMultiCommand { private static _createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: RedisSentinelMultiCommand, ...args: Array) { - const redisArgs = command.transformArguments(...args); + let redisArgs: CommandArguments = []; + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; + } else { + redisArgs = command.transformArguments(...args); + } + return this.addCommand( command.IS_READ_ONLY, redisArgs, @@ -99,8 +110,19 @@ export default class RedisSentinelMultiCommand { private static _createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: { _self: RedisSentinelMultiCommand }, ...args: Array) { - const redisArgs = command.transformArguments(...args); + let redisArgs: CommandArguments = []; + + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; + } else { + redisArgs = command.transformArguments(...args); + } + return this._self.addCommand( command.IS_READ_ONLY, redisArgs, @@ -110,12 +132,24 @@ export default class RedisSentinelMultiCommand { } private static _createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return function (this: { _self: RedisSentinelMultiCommand }, ...args: Array) { - const fnArgs = fn.transformArguments(...args); + let fnArgs: CommandArguments = []; + + if (fn.parseCommand) { + const parser = new BasicCommandParser(resp); + fn.parseCommand(parser, ...args); + fnArgs = parser.redisArgs; + fnArgs.preserve = parser.preserve; + } else { + fnArgs = fn.transformArguments(...args); + } + const redisArgs: CommandArguments = prefix.concat(fnArgs); redisArgs.preserve = fnArgs.preserve; + return this._self.addCommand( fn.IS_READ_ONLY, redisArgs, @@ -126,8 +160,19 @@ export default class RedisSentinelMultiCommand { private static _createScriptCommand(script: RedisScript, resp: RespVersions) { const transformReply = getTransformReply(script, resp); + return function (this: RedisSentinelMultiCommand, ...args: Array) { - const scriptArgs = script.transformArguments(...args); + let scriptArgs: CommandArguments = []; + + if (script.parseCommand) { + const parser = new BasicCommandParser(resp); + script.parseCommand(parser, ...args); + scriptArgs = parser.redisArgs; + scriptArgs.preserve = parser.preserve; + } else { + scriptArgs = script.transformArguments(...args); + } + this._setState( script.IS_READ_ONLY ); @@ -136,6 +181,7 @@ export default class RedisSentinelMultiCommand { scriptArgs, transformReply ); + return this; }; } From c4328b4f5c502fc7e3d744dcd4f52094f8f21e6a Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Thu, 21 Mar 2024 15:15:56 +0200 Subject: [PATCH 4/9] change how prefix is handled for script/functions --- packages/client/lib/client/index.ts | 16 +++++++--------- packages/client/lib/client/multi-command.ts | 14 +++++++++----- packages/client/lib/client/pool.ts | 10 ++++++---- packages/client/lib/cluster/index.ts | 10 ++++++---- packages/client/lib/sentinel/utils.ts | 10 ++++++---- 5 files changed, 34 insertions(+), 26 deletions(-) diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 2bb0a8423db..ecc699d2a1d 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -158,7 +158,7 @@ export default class RedisClient< const parser = this._self.#newCommandParser(resp); command.parseCommand(parser, ...args); - return this.executeCommand(undefined, parser, this._commandOptions, transformReply); + return this.executeCommand(parser, this._commandOptions, transformReply); } else { const redisArgs = command.transformArguments(...args), reply = await this.sendCommand(redisArgs, this._commandOptions); @@ -177,7 +177,7 @@ export default class RedisClient< const parser = this._self._self.#newCommandParser(resp); command.parseCommand(parser, ...args); - return this._self.executeCommand(undefined, parser, this._self._commandOptions, transformReply); + return this._self.executeCommand(parser, this._self._commandOptions, transformReply); } else { const redisArgs = command.transformArguments(...args), reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); @@ -195,9 +195,10 @@ export default class RedisClient< return async function (this: NamespaceProxyClient, ...args: Array) { if (fn.parseCommand) { const parser = this._self._self.#newCommandParser(resp); + parser.pushVariadic(prefix); fn.parseCommand(parser, ...args); - return this._self.executeCommand(prefix, parser, this._self._commandOptions, transformReply); + return this._self.executeCommand(parser, this._self._commandOptions, transformReply); } else { const fnArgs = fn.transformArguments(...args), reply = await this._self.sendCommand( @@ -218,9 +219,10 @@ export default class RedisClient< return async function (this: ProxyClient, ...args: Array) { if (script.parseCommand) { const parser = this._self.#newCommandParser(resp); + parser.pushVariadic(prefix); script.parseCommand(parser, ...args); - return this.executeCommand(prefix, parser, this._commandOptions, transformReply); + return this.executeCommand(parser, this._commandOptions, transformReply); } else { const scriptArgs = script.transformArguments(...args), redisArgs = prefix.concat(scriptArgs), @@ -611,15 +613,11 @@ export default class RedisClient< } async executeCommand( - prefix: Array | undefined, parser: CommandParser, commandOptions: CommandOptions | undefined, transformReply: TransformReply | undefined ) { - const redisArgs = prefix ? prefix.concat(parser.redisArgs) : parser.redisArgs; - const fn = () => { return this.sendCommand(redisArgs, commandOptions) }; - - const reply = await fn(); + const reply = await this.sendCommand(parser.redisArgs, commandOptions); if (transformReply) { return transformReply(reply, parser.preserve); diff --git a/packages/client/lib/client/multi-command.ts b/packages/client/lib/client/multi-command.ts index b02078157c7..b59d4913d5e 100644 --- a/packages/client/lib/client/multi-command.ts +++ b/packages/client/lib/client/multi-command.ts @@ -1,7 +1,7 @@ import COMMANDS from '../commands'; import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType, RedisMultiQueuedCommand } from '../multi-command'; import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping } from '../RESP/types'; -import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; +import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander'; import { BasicCommandParser } from './parser'; type CommandSignature< @@ -96,6 +96,7 @@ export default class RedisClientMultiCommand { if (command.parseCommand) { const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; redisArgs.preserve = parser.preserve; } else { @@ -118,6 +119,7 @@ export default class RedisClientMultiCommand { if (command.parseCommand) { const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; redisArgs.preserve = parser.preserve; } else { @@ -140,7 +142,9 @@ export default class RedisClientMultiCommand { if (fn.parseCommand) { const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); fn.parseCommand(parser, ...args); + fnArgs = parser.redisArgs; fnArgs.preserve = parser.preserve; } else { @@ -158,6 +162,7 @@ export default class RedisClientMultiCommand { } static #createScriptCommand(script: RedisScript, resp: RespVersions) { + const prefix = scriptArgumentsPrefix(script); const transformReply = getTransformReply(script, resp); return function (this: RedisClientMultiCommand, ...args: Array) { @@ -165,20 +170,19 @@ export default class RedisClientMultiCommand { if (script.parseCommand) { const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); script.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; redisArgs.preserve = parser.preserve; } else { redisArgs = script.transformArguments(...args); } - this.#multi.addScript( - script, + return this.addCommand( redisArgs, transformReply ); - - return this; }; } diff --git a/packages/client/lib/client/pool.ts b/packages/client/lib/client/pool.ts index 2167f0dd011..1cdd609d8a9 100644 --- a/packages/client/lib/client/pool.ts +++ b/packages/client/lib/client/pool.ts @@ -67,7 +67,7 @@ export class RedisClientPool< const parser = this._self.#newCommandParser(resp); command.parseCommand(parser, ...args); - return this.execute(client => client.executeCommand(undefined, parser, this._commandOptions, transformReply)) + return this.execute(client => client.executeCommand(parser, this._commandOptions, transformReply)) } else { const redisArgs = command.transformArguments(...args), reply = await this.sendCommand(redisArgs, this._commandOptions); @@ -86,7 +86,7 @@ export class RedisClientPool< const parser = this._self._self.#newCommandParser(resp); command.parseCommand(parser, ...args); - return this._self.execute(client => client.executeCommand(undefined, parser, this._self._commandOptions, transformReply)) + return this._self.execute(client => client.executeCommand(parser, this._self._commandOptions, transformReply)) } else { const redisArgs = command.transformArguments(...args), reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); @@ -104,9 +104,10 @@ export class RedisClientPool< return async function (this: NamespaceProxyPool, ...args: Array) { if (fn.parseCommand) { const parser = this._self.#newCommandParser(resp); + parser.pushVariadic(prefix); fn.parseCommand(parser, ...args); - return this._self.execute(client => client.executeCommand(prefix, parser, this._self._commandOptions, transformReply)) + return this._self.execute(client => client.executeCommand(parser, this._self._commandOptions, transformReply)) } else { const fnArgs = fn.transformArguments(...args), reply = await this._self.sendCommand( @@ -127,9 +128,10 @@ export class RedisClientPool< return async function (this: ProxyPool, ...args: Array) { if (script.parseCommand) { const parser = this._self.#newCommandParser(resp); + parser.pushVariadic(prefix); script.parseCommand(parser, ...args); - return this.execute(client => client.executeCommand(prefix, parser, this._commandOptions, transformReply)) + return this.execute(client => client.executeCommand(parser, this._commandOptions, transformReply)) } else { const scriptArgs = script.transformArguments(...args), redisArgs = prefix.concat(scriptArgs), diff --git a/packages/client/lib/cluster/index.ts b/packages/client/lib/cluster/index.ts index 9a1403e4f05..a7dfa9b7147 100644 --- a/packages/client/lib/cluster/index.ts +++ b/packages/client/lib/cluster/index.ts @@ -181,7 +181,7 @@ export default class RedisCluster< parser.firstKey, command.IS_READ_ONLY, this._commandOptions, - (client, opts) => client.executeCommand(undefined, parser, opts, transformReply) + (client, opts) => client.executeCommand(parser, opts, transformReply) ); } else { const redisArgs = command.transformArguments(...args); @@ -218,7 +218,7 @@ export default class RedisCluster< parser.firstKey, command.IS_READ_ONLY, this._self._commandOptions, - (client, opts) => client.executeCommand(undefined, parser, opts, transformReply) + (client, opts) => client.executeCommand(parser, opts, transformReply) ); } else { const redisArgs = command.transformArguments(...args); @@ -249,13 +249,14 @@ export default class RedisCluster< return async function (this: NamespaceProxyCluster, ...args: Array) { if (fn.parseCommand) { const parser = this._self._self.#newCommandParser(resp); + parser.pushVariadic(prefix); fn.parseCommand(parser, ...args); return this._self.#execute( parser.firstKey, fn.IS_READ_ONLY, this._self._commandOptions, - (client, opts) => client.executeCommand(prefix, parser, opts, transformReply) + (client, opts) => client.executeCommand(parser, opts, transformReply) ); } else { const fnArgs = fn.transformArguments(...args); @@ -287,13 +288,14 @@ export default class RedisCluster< return async function (this: ProxyCluster, ...args: Array) { if (script.parseCommand) { const parser = this._self.#newCommandParser(resp); + parser.pushVariadic(prefix); script.parseCommand(parser, ...args); return this._self.#execute( parser.firstKey, script.IS_READ_ONLY, this._commandOptions, - (client, opts) => client.executeCommand(prefix, parser, opts, transformReply) + (client, opts) => client.executeCommand(parser, opts, transformReply) ); } else { const scriptArgs = script.transformArguments(...args), diff --git a/packages/client/lib/sentinel/utils.ts b/packages/client/lib/sentinel/utils.ts index 2dc27669fc7..0e01bb178ee 100644 --- a/packages/client/lib/sentinel/utils.ts +++ b/packages/client/lib/sentinel/utils.ts @@ -45,7 +45,7 @@ export function createCommand(com return this._self._execute( command.IS_READ_ONLY, - client => client.executeCommand(undefined, parser, this.commandOptions, transformReply) + client => client.executeCommand(parser, this.commandOptions, transformReply) ); } else { const redisArgs = command.transformArguments(...args); @@ -69,11 +69,12 @@ export function createFunctionCommand) { if (fn.parseCommand) { const parser = this._self._self.newCommandParser(resp); + parser.pushVariadic(prefix); fn.parseCommand(parser, ...args); return this._self._execute( fn.IS_READ_ONLY, - client => client.executeCommand(prefix, parser, this._self.commandOptions, transformReply) + client => client.executeCommand(parser, this._self.commandOptions, transformReply) ); } else { const fnArgs = fn.transformArguments(...args), @@ -101,7 +102,7 @@ export function createModuleCommand client.executeCommand(undefined, parser, this._self.commandOptions, transformReply) + client => client.executeCommand(parser, this._self.commandOptions, transformReply) ); } else { const redisArgs = command.transformArguments(...args), @@ -125,11 +126,12 @@ export function createScriptCommand) { if (script.parseCommand) { const parser = this._self.newCommandParser(resp); + parser.pushVariadic(prefix); script.parseCommand(parser, ...args); return this._self._execute( script.IS_READ_ONLY, - client => client.executeCommand(prefix, parser, this.commandOptions, transformReply) + client => client.executeCommand(parser, this.commandOptions, transformReply) ); } else { const scriptArgs = script.transformArguments(...args), From 2fd1585dbd6078a2e2e28694ee8439a584ef2890 Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Mon, 25 Mar 2024 12:21:28 +0200 Subject: [PATCH 5/9] simplify parser, only have a single parser. --- packages/client/lib/client/parser.ts | 72 ++++++++++++++++++---------- packages/client/lib/cluster/index.ts | 7 +-- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/packages/client/lib/client/parser.ts b/packages/client/lib/client/parser.ts index e462f542b65..475ac0538b0 100644 --- a/packages/client/lib/client/parser.ts +++ b/packages/client/lib/client/parser.ts @@ -2,22 +2,29 @@ import { RedisArgument, RespVersions } from "../.."; import { RedisVariadicArgument } from "../commands/generic-transformers"; export interface CommandParser { - redisArgs: Array; + redisArgs: ReadonlyArray; + keys: ReadonlyArray; + firstKey: RedisArgument | undefined; respVersion: RespVersions; preserve: unknown; + cachable: boolean; push: (arg: RedisArgument) => unknown; pushVariadic: (vals: RedisVariadicArgument) => unknown; + pushVariadicNumber: (vals: number | Array) => unknown; pushKey: (key: RedisArgument) => unknown; // normal push of keys pushKeys: (keys: RedisVariadicArgument) => unknown; // push multiple keys at a time + pushKeysLength: (keys: RedisVariadicArgument) => unknown; // push multiple keys at a time setCachable: () => unknown; setPreserve: (val: unknown) => unknown; } -export abstract class AbstractCommandParser implements CommandParser { +export class BasicCommandParser implements CommandParser { #redisArgs: Array = []; + #keys: Array = []; #respVersion: RespVersions; #preserve: unknown; + #cachable: boolean = false; constructor(respVersion: RespVersions = 2) { this.#respVersion = respVersion; @@ -27,6 +34,14 @@ export abstract class AbstractCommandParser implements CommandParser { return this.#redisArgs; } + get keys() { + return this.#keys; + } + + get firstKey() { + return this.#keys.length != 0 ? this.#keys[0] : undefined; + } + get respVersion() { return this.#respVersion; } @@ -35,9 +50,12 @@ export abstract class AbstractCommandParser implements CommandParser { return this.#preserve; } + get cachable() { + return this.#cachable + } + push(arg: RedisArgument) { this.#redisArgs.push(arg); - }; pushVariadic(vals: RedisVariadicArgument) { @@ -50,14 +68,36 @@ export abstract class AbstractCommandParser implements CommandParser { } } + pushVariadicNumber(vals: number | number[]) { + if (Array.isArray(vals)) { + for (const val of vals) { + this.push(val.toString()); + } + } else { + this.push(vals.toString()); + } + } + pushKey(key: RedisArgument) { + this.#keys.push(key); this.#redisArgs.push(key); }; + pushKeysLength(keys: RedisVariadicArgument) { + if (Array.isArray(keys)) { + this.#redisArgs.push(keys.length.toString()); + } else { + this.#redisArgs.push('1'); + } + this.pushKeys(keys); + } + pushKeys(keys: RedisVariadicArgument) { if (Array.isArray(keys)) { + this.#keys.push(...keys); this.#redisArgs.push(...keys); } else { + this.#keys.push(keys); this.#redisArgs.push(keys); } } @@ -66,27 +106,7 @@ export abstract class AbstractCommandParser implements CommandParser { this.#preserve = val; } - setCachable() {}; -} - -/* Note: I do it this way, where BasicCommandParser extends Abstract without any changes, - and CachedCommandParser extends Abstract with changes, to enable them to be easily - distinguishable at runtime. If Cached extended Basic, then Cached would also be a Basic, - thereby making them harder to distinguish. -*/ -export class BasicCommandParser extends AbstractCommandParser {}; - -export interface ClusterCommandParser extends CommandParser { - firstKey: RedisArgument | undefined; -} - -export class BasicClusterCommandParser extends BasicCommandParser implements ClusterCommandParser { - firstKey: RedisArgument | undefined; - - override pushKey(key: RedisArgument): void { - if (!this.firstKey) { - this.firstKey = key; - } - super.pushKey(key); - } + setCachable() { + this.#cachable = true; + }; } \ No newline at end of file diff --git a/packages/client/lib/cluster/index.ts b/packages/client/lib/cluster/index.ts index a7dfa9b7147..d1362f0128e 100644 --- a/packages/client/lib/cluster/index.ts +++ b/packages/client/lib/cluster/index.ts @@ -10,7 +10,8 @@ import { PubSubListener } from '../client/pub-sub'; import { ErrorReply } from '../errors'; import { RedisTcpSocketOptions } from '../client/socket'; import ASKING from '../commands/ASKING'; -import { BasicClusterCommandParser, ClusterCommandParser } from '../client/parser'; +import { BasicCommandParser, CommandParser } from '../client/parser'; +; interface ClusterCommander< M extends RedisModules, @@ -408,8 +409,8 @@ export default class RedisCluster< return this._self.#slots.isOpen; } - #newCommandParser(resp: RespVersions): ClusterCommandParser { - return new BasicClusterCommandParser(resp); + #newCommandParser(resp: RespVersions): CommandParser { + return new BasicCommandParser(resp); } constructor(options: RedisClusterOptions) { From df228c961f7fe714a32736ddb9abac4da06eec5a Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Sat, 30 Mar 2024 21:30:45 +0300 Subject: [PATCH 6/9] nit --- packages/client/lib/cluster/multi-command.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/lib/cluster/multi-command.ts b/packages/client/lib/cluster/multi-command.ts index 851f217bf0d..3f7df07b085 100644 --- a/packages/client/lib/cluster/multi-command.ts +++ b/packages/client/lib/cluster/multi-command.ts @@ -3,7 +3,7 @@ import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType, RedisMultiQ import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping, RedisArgument } from '../RESP/types'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; import RedisCluster from '.'; -import { BasicClusterCommandParser } from '../client/parser'; +import { BasicCommandParser } from '../client/parser'; type CommandSignature< REPLIES extends Array, @@ -100,7 +100,7 @@ export default class RedisClusterMultiCommand { let firstKey: RedisArgument | undefined; if (command.parseCommand) { - const parser = new BasicClusterCommandParser(resp); + const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); redisArgs = parser.redisArgs; redisArgs.preserve = parser.preserve; @@ -131,7 +131,7 @@ export default class RedisClusterMultiCommand { let firstKey: RedisArgument | undefined; if (command.parseCommand) { - const parser = new BasicClusterCommandParser(resp); + const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); redisArgs = parser.redisArgs; redisArgs.preserve = parser.preserve; @@ -162,7 +162,7 @@ export default class RedisClusterMultiCommand { let firstKey: RedisArgument | undefined; if (fn.parseCommand) { - const parser = new BasicClusterCommandParser(resp); + const parser = new BasicCommandParser(resp); fn.parseCommand(parser, ...args); fnArgs = parser.redisArgs; fnArgs.preserve = parser.preserve; @@ -196,7 +196,7 @@ export default class RedisClusterMultiCommand { let firstKey: RedisArgument | undefined; if (script.parseCommand) { - const parser = new BasicClusterCommandParser(resp); + const parser = new BasicCommandParser(resp); script.parseCommand(parser, ...args); scriptArgs = parser.redisArgs; scriptArgs.preserve = parser.preserve; From d96aacdcb17bd9f015ebad4f42e4286176aff540 Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Mon, 25 Mar 2024 12:41:22 +0200 Subject: [PATCH 7/9] move some usage of RedisArgument arrays to ReadonlyArray motivated by wanting to return a read only array from the parser object --- packages/client/lib/RESP/encoder.ts | 2 +- packages/client/lib/client/commands-queue.ts | 8 ++++---- packages/client/lib/client/index.ts | 2 +- packages/client/lib/client/socket.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/client/lib/RESP/encoder.ts b/packages/client/lib/RESP/encoder.ts index af857711dc3..995650627f1 100644 --- a/packages/client/lib/RESP/encoder.ts +++ b/packages/client/lib/RESP/encoder.ts @@ -2,7 +2,7 @@ import { RedisArgument } from './types'; const CRLF = '\r\n'; -export default function encodeCommand(args: Array): Array { +export default function encodeCommand(args: ReadonlyArray): ReadonlyArray { const toWrite: Array = []; let strings = '*' + args.length + CRLF; diff --git a/packages/client/lib/client/commands-queue.ts b/packages/client/lib/client/commands-queue.ts index a4029779fc8..15e8a747b98 100644 --- a/packages/client/lib/client/commands-queue.ts +++ b/packages/client/lib/client/commands-queue.ts @@ -1,7 +1,7 @@ import { SinglyLinkedList, DoublyLinkedNode, DoublyLinkedList } from './linked-list'; import encodeCommand from '../RESP/encoder'; import { Decoder, PUSH_TYPE_MAPPING, RESP_TYPES } from '../RESP/decoder'; -import { CommandArguments, TypeMapping, ReplyUnion, RespVersions } from '../RESP/types'; +import { TypeMapping, ReplyUnion, RespVersions, RedisArgument } from '../RESP/types'; import { ChannelListeners, PubSub, PubSubCommand, PubSubListener, PubSubType, PubSubTypeListeners } from './pub-sub'; import { AbortError, ErrorReply } from '../errors'; import { MonitorCallback } from '.'; @@ -17,7 +17,7 @@ export interface CommandOptions { } export interface CommandToWrite extends CommandWaitingForReply { - args: CommandArguments; + args: ReadonlyArray; chainId: symbol | undefined; abort: { signal: AbortSignal; @@ -117,7 +117,7 @@ export default class RedisCommandsQueue { } addCommand( - args: CommandArguments, + args: ReadonlyArray, options?: CommandOptions ): Promise { if (this.#maxLength && this.#toWrite.length + this.#waitingForReply.length >= this.#maxLength) { @@ -346,7 +346,7 @@ export default class RedisCommandsQueue { *commandsToWrite() { let toSend = this.#toWrite.shift(); while (toSend) { - let encoded: CommandArguments; + let encoded: ReadonlyArray try { encoded = encodeCommand(toSend.args); } catch (err) { diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index ecc699d2a1d..4346f265950 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -627,7 +627,7 @@ export default class RedisClient< } sendCommand( - args: Array, + args: ReadonlyArray, options?: CommandOptions ): Promise { if (!this._self.#socket.isOpen) { diff --git a/packages/client/lib/client/socket.ts b/packages/client/lib/client/socket.ts index dcadad4c3dd..384dd7364e9 100644 --- a/packages/client/lib/client/socket.ts +++ b/packages/client/lib/client/socket.ts @@ -271,7 +271,7 @@ export default class RedisSocket extends EventEmitter { }); } - write(iterable: Iterable>) { + write(iterable: Iterable>) { if (!this.#socket) return; this.#socket.cork(); From 657167cfd7209cbb3a38dc0d056a8c61641c6a2e Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Wed, 3 Apr 2024 14:39:05 +0300 Subject: [PATCH 8/9] address review comments --- package-lock.json | 34 +++++++++++-- packages/client/lib/client/index.ts | 13 ++--- packages/client/lib/client/multi-command.ts | 3 +- packages/client/lib/client/pool.ts | 13 ++--- packages/client/lib/cluster/index.ts | 49 +++++++------------ packages/client/lib/commands/GET.spec.ts | 3 +- .../lib/commands/generic-transformers.ts | 30 +++++++++++- packages/client/lib/sentinel/index.ts | 9 ---- packages/client/lib/sentinel/utils.ts | 9 ++-- packages/client/lib/test-utils.ts | 19 ------- packages/client/package.json | 1 + 11 files changed, 96 insertions(+), 87 deletions(-) diff --git a/package-lock.json b/package-lock.json index 312ece6a26c..3fffdb6d2f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1376,9 +1376,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", - "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "version": "20.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz", + "integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -8127,16 +8127,21 @@ } }, "packages/bloom": { + "name": "@redis/bloom", "version": "2.0.0-next.3", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } }, "packages/client": { + "name": "@redis/client", "version": "2.0.0-next.4", "license": "MIT", "dependencies": { @@ -8144,29 +8149,38 @@ }, "devDependencies": { "@redis/test-utils": "*", + "@types/node": "^20.12.2", "@types/sinon": "^17.0.3", "sinon": "^17.0.1" }, "engines": { - "node": ">=14" + "node": ">= 18" } }, "packages/graph": { + "name": "@redis/graph", "version": "2.0.0-next.2", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } }, "packages/json": { + "name": "@redis/json", "version": "2.0.0-next.2", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } @@ -8181,19 +8195,27 @@ "@redis/json": "2.0.0-next.2", "@redis/search": "2.0.0-next.2", "@redis/time-series": "2.0.0-next.2" + }, + "engines": { + "node": ">= 18" } }, "packages/search": { + "name": "@redis/search", "version": "2.0.0-next.2", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } }, "packages/test-utils": { + "name": "@redis/test-utils", "devDependencies": { "@types/yargs": "^17.0.32", "yargs": "^17.7.2" @@ -8261,11 +8283,15 @@ } }, "packages/time-series": { + "name": "@redis/time-series", "version": "2.0.0-next.2", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 4346f265950..e1f7fa0592a 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -155,7 +155,8 @@ export default class RedisClient< return async function (this: ProxyClient, ...args: Array) { if (command.parseCommand) { - const parser = this._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); return this.executeCommand(parser, this._commandOptions, transformReply); @@ -174,7 +175,7 @@ export default class RedisClient< return async function (this: NamespaceProxyClient, ...args: Array) { if (command.parseCommand) { - const parser = this._self._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); return this._self.executeCommand(parser, this._self._commandOptions, transformReply); @@ -194,7 +195,7 @@ export default class RedisClient< return async function (this: NamespaceProxyClient, ...args: Array) { if (fn.parseCommand) { - const parser = this._self._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); parser.pushVariadic(prefix); fn.parseCommand(parser, ...args); @@ -218,7 +219,7 @@ export default class RedisClient< return async function (this: ProxyClient, ...args: Array) { if (script.parseCommand) { - const parser = this._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); parser.pushVariadic(prefix); script.parseCommand(parser, ...args); @@ -344,10 +345,6 @@ export default class RedisClient< this._self.#dirtyWatch = msg; } - #newCommandParser(resp: RespVersions): CommandParser { - return new BasicCommandParser(resp); - } - constructor(options?: RedisClientOptions) { super(); this.#options = this.#initiateOptions(options); diff --git a/packages/client/lib/client/multi-command.ts b/packages/client/lib/client/multi-command.ts index b59d4913d5e..9b800599b43 100644 --- a/packages/client/lib/client/multi-command.ts +++ b/packages/client/lib/client/multi-command.ts @@ -176,7 +176,8 @@ export default class RedisClientMultiCommand { redisArgs = parser.redisArgs; redisArgs.preserve = parser.preserve; } else { - redisArgs = script.transformArguments(...args); + redisArgs = prefix; + redisArgs.push(...script.transformArguments(...args)); } return this.addCommand( diff --git a/packages/client/lib/client/pool.ts b/packages/client/lib/client/pool.ts index 1cdd609d8a9..5822e418b1b 100644 --- a/packages/client/lib/client/pool.ts +++ b/packages/client/lib/client/pool.ts @@ -7,7 +7,7 @@ import { TimeoutError } from '../errors'; import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander'; import { CommandOptions } from './commands-queue'; import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command'; -import { CommandParser, BasicCommandParser } from './parser'; +import { BasicCommandParser } from './parser'; export interface RedisPoolOptions { /** @@ -64,7 +64,7 @@ export class RedisClientPool< return async function (this: ProxyPool, ...args: Array) { if (command.parseCommand) { - const parser = this._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); return this.execute(client => client.executeCommand(parser, this._commandOptions, transformReply)) @@ -83,7 +83,7 @@ export class RedisClientPool< return async function (this: NamespaceProxyPool, ...args: Array) { if (command.parseCommand) { - const parser = this._self._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); return this._self.execute(client => client.executeCommand(parser, this._self._commandOptions, transformReply)) @@ -103,7 +103,7 @@ export class RedisClientPool< return async function (this: NamespaceProxyPool, ...args: Array) { if (fn.parseCommand) { - const parser = this._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); parser.pushVariadic(prefix); fn.parseCommand(parser, ...args); @@ -127,7 +127,7 @@ export class RedisClientPool< return async function (this: ProxyPool, ...args: Array) { if (script.parseCommand) { - const parser = this._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); parser.pushVariadic(prefix); script.parseCommand(parser, ...args); @@ -242,9 +242,6 @@ export class RedisClientPool< return this._self.#isClosing; } - #newCommandParser(resp: RespVersions): CommandParser { - return new BasicCommandParser(resp); - } /** * You are probably looking for {@link RedisClient.createPool `RedisClient.createPool`}, diff --git a/packages/client/lib/cluster/index.ts b/packages/client/lib/cluster/index.ts index d1362f0128e..65c009bb6a6 100644 --- a/packages/client/lib/cluster/index.ts +++ b/packages/client/lib/cluster/index.ts @@ -10,7 +10,8 @@ import { PubSubListener } from '../client/pub-sub'; import { ErrorReply } from '../errors'; import { RedisTcpSocketOptions } from '../client/socket'; import ASKING from '../commands/ASKING'; -import { BasicCommandParser, CommandParser } from '../client/parser'; +import { BasicCommandParser } from '../client/parser'; +import { parseArgs } from '../commands/generic-transformers'; ; interface ClusterCommander< @@ -175,7 +176,7 @@ export default class RedisCluster< return async function (this: ProxyCluster, ...args: Array) { if (command.parseCommand) { - const parser = this._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); return this._self.#execute( @@ -212,7 +213,7 @@ export default class RedisCluster< return async function (this: NamespaceProxyCluster, ...args: Array) { if (command.parseCommand) { - const parser = this._self._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); return this._self.#execute( @@ -249,7 +250,7 @@ export default class RedisCluster< return async function (this: NamespaceProxyCluster, ...args: Array) { if (fn.parseCommand) { - const parser = this._self._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); parser.pushVariadic(prefix); fn.parseCommand(parser, ...args); @@ -288,7 +289,7 @@ export default class RedisCluster< return async function (this: ProxyCluster, ...args: Array) { if (script.parseCommand) { - const parser = this._self.#newCommandParser(resp); + const parser = new BasicCommandParser(resp); parser.pushVariadic(prefix); script.parseCommand(parser, ...args); @@ -409,10 +410,6 @@ export default class RedisCluster< return this._self.#slots.isOpen; } - #newCommandParser(resp: RespVersions): CommandParser { - return new BasicCommandParser(resp); - } - constructor(options: RedisClusterOptions) { super(); @@ -495,25 +492,6 @@ export default class RedisCluster< // return this._commandOptionsProxy('policies', policies); // } - #handleAsk( - fn: (client: RedisClientType, opts?: ClusterCommandOptions) => Promise - ) { - return async (client: RedisClientType, options?: ClusterCommandOptions) => { - const chainId = Symbol("asking chain"); - const opts = options ? {...options} : {}; - opts.chainId = chainId; - - const ret = await Promise.all( - [ - client.sendCommand(ASKING.transformArguments(), {chainId: chainId}), - fn(client, opts) - ] - ); - - return ret[1]; - }; - } - async #execute( firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, @@ -523,13 +501,14 @@ export default class RedisCluster< const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16; let client = await this.#slots.getClient(firstKey, isReadonly); let i = 0; - let myFn = fn; + let myOpts = options; while (true) { try { - return await myFn(client, options); + return await fn(client, myOpts); } catch (err) { - myFn = fn; + // reset to passed in options, if changed by an ask request + myOpts = options; // TODO: error class if (++i > maxCommandRedirections || !(err instanceof Error)) { throw err; @@ -547,8 +526,14 @@ export default class RedisCluster< throw new Error(`Cannot find node ${address}`); } - myFn = this.#handleAsk(fn); client = redirectTo; + + const chainId = Symbol('Asking Chain'); + const myOpts = options ? {...options} : {}; + myOpts.chainId = chainId; + + client.sendCommand(parseArgs(ASKING), {chainId: chainId}).catch(err => { console.log(`Asking Failed: ${err}`) } ); + continue; } diff --git a/packages/client/lib/commands/GET.spec.ts b/packages/client/lib/commands/GET.spec.ts index f6a5ff3ef8f..3e630d03e0b 100644 --- a/packages/client/lib/commands/GET.spec.ts +++ b/packages/client/lib/commands/GET.spec.ts @@ -1,5 +1,6 @@ import { strict as assert } from 'node:assert'; -import testUtils, { GLOBAL, parseArgs } from '../test-utils'; +import testUtils, { GLOBAL } from '../test-utils'; +import { parseArgs } from './generic-transformers'; import GET from './GET'; describe('GET', () => { diff --git a/packages/client/lib/commands/generic-transformers.ts b/packages/client/lib/commands/generic-transformers.ts index a96e79a9c51..d16e7b4cd32 100644 --- a/packages/client/lib/commands/generic-transformers.ts +++ b/packages/client/lib/commands/generic-transformers.ts @@ -1,4 +1,5 @@ -import { UnwrapReply, ArrayReply, BlobStringReply, BooleanReply, CommandArguments, DoubleReply, NullReply, NumberReply, RedisArgument, TuplesReply } from '../RESP/types'; +import { UnwrapReply, ArrayReply, BlobStringReply, BooleanReply, CommandArguments, DoubleReply, NullReply, NumberReply, RedisArgument, TuplesReply, Command } from '../RESP/types'; +import { BasicCommandParser } from '../client/parser'; export function isNullReply(reply: unknown): reply is NullReply { return reply === null; @@ -446,3 +447,30 @@ function isPlainKey(key: RedisArgument | ZKeyAndWeight): key is RedisArgument { function isPlainKeys(keys: Array | Array): keys is Array { return isPlainKey(keys[0]); } + +/** + * @deprecated + */ +export function parseArgs(command: Command, ...args: Array) { + if (command.parseCommand) { + const parser = new BasicCommandParser(); + command.parseCommand!(parser, ...args); + + const redisArgs: CommandArguments = parser.redisArgs; + if (parser.preserve) { + redisArgs.preserve = parser.preserve; + } + return redisArgs; + } else { + return command.transformArguments(...args); + } +} + +/** + * @deprecated + */ +export function parseArgsWith(command: Command, ...args: Array) { + const parser = new BasicCommandParser(); + command.parseCommand!(parser, ...args); + return parser.preserve; +} diff --git a/packages/client/lib/sentinel/index.ts b/packages/client/lib/sentinel/index.ts index d3c0465e60f..57819133e0c 100644 --- a/packages/client/lib/sentinel/index.ts +++ b/packages/client/lib/sentinel/index.ts @@ -16,7 +16,6 @@ import { RedisVariadicArgument } from '../commands/generic-transformers'; import { WaitQueue } from './wait-queue'; import { TcpNetConnectOpts } from 'node:net'; import { RedisTcpSocketOptions } from '../client/socket'; -import { CommandParser, BasicCommandParser } from '../client/parser'; interface ClientInfo { id: number; @@ -47,10 +46,6 @@ export class RedisSentinelClient< #commandOptions?: CommandOptions; - newCommandParser(resp: RespVersions): CommandParser { - return new BasicCommandParser(resp); - } - constructor( internal: RedisSentinelInternal, clientInfo: ClientInfo, @@ -282,10 +277,6 @@ export default class RedisSentinel< #masterClientCount = 0; #masterClientInfo?: ClientInfo; - newCommandParser(resp: RespVersions): CommandParser { - return new BasicCommandParser(resp); - } - constructor(options: RedisSentinelOptions) { super(); diff --git a/packages/client/lib/sentinel/utils.ts b/packages/client/lib/sentinel/utils.ts index 0e01bb178ee..b8f15e30e76 100644 --- a/packages/client/lib/sentinel/utils.ts +++ b/packages/client/lib/sentinel/utils.ts @@ -1,4 +1,5 @@ import { Command, RedisFunction, RedisScript, RespVersions } from '../RESP/types'; +import { BasicCommandParser } from '../client/parser'; import { RedisSocketOptions, RedisTcpSocketOptions } from '../client/socket'; import { functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander'; import { NamespaceProxySentinel, NamespaceProxySentinelClient, NodeInfo, ProxySentinel, ProxySentinelClient, RedisNode } from './types'; @@ -40,7 +41,7 @@ export function createCommand(com return async function (this: T, ...args: Array) { if (command.parseCommand) { - const parser = this._self.newCommandParser(resp); + const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); return this._self._execute( @@ -68,7 +69,7 @@ export function createFunctionCommand) { if (fn.parseCommand) { - const parser = this._self._self.newCommandParser(resp); + const parser = new BasicCommandParser(resp); parser.pushVariadic(prefix); fn.parseCommand(parser, ...args); @@ -97,7 +98,7 @@ export function createModuleCommand) { if (command.parseCommand) { - const parser = this._self._self.newCommandParser(resp); + const parser = new BasicCommandParser(resp); command.parseCommand(parser, ...args); return this._self._execute( @@ -125,7 +126,7 @@ export function createScriptCommand) { if (script.parseCommand) { - const parser = this._self.newCommandParser(resp); + const parser = new BasicCommandParser(resp); parser.pushVariadic(prefix); script.parseCommand(parser, ...args); diff --git a/packages/client/lib/test-utils.ts b/packages/client/lib/test-utils.ts index f1a2b6c5059..81aac6f9b03 100644 --- a/packages/client/lib/test-utils.ts +++ b/packages/client/lib/test-utils.ts @@ -1,8 +1,6 @@ import TestUtils from '@redis/test-utils'; import { SinonSpy } from 'sinon'; import { setTimeout } from 'node:timers/promises'; -import { Command, CommandArguments } from './RESP/types'; -import { BasicCommandParser } from './client/parser'; const utils = new TestUtils({ dockerImageName: 'redis', @@ -69,20 +67,3 @@ export const BLOCKING_MIN_VALUE = ( utils.isVersionGreaterThan([6]) ? 0.01 : 1 ); - -export function parseArgs(command: Command, ...args: Array) { - const parser = new BasicCommandParser(); - command.parseCommand!(parser, ...args); - - const redisArgs: CommandArguments = parser.redisArgs; - if (parser.preserve) { - redisArgs.preserve = parser.preserve; - } - return redisArgs; -} - -export function parseArgsWith(command: Command, ...args: Array) { - const parser = new BasicCommandParser(); - command.parseCommand!(parser, ...args); - return parser.preserve; -} \ No newline at end of file diff --git a/packages/client/package.json b/packages/client/package.json index cb82f67bd53..3a5fb67902e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@redis/test-utils": "*", + "@types/node": "^20.12.2", "@types/sinon": "^17.0.3", "sinon": "^17.0.1" }, From 12d7b3008837d661bfc9c1a0aa49043c408101e4 Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Thu, 16 May 2024 10:07:29 -0700 Subject: [PATCH 9/9] Update parser.ts --- packages/client/lib/client/parser.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/lib/client/parser.ts b/packages/client/lib/client/parser.ts index 475ac0538b0..ae96045d146 100644 --- a/packages/client/lib/client/parser.ts +++ b/packages/client/lib/client/parser.ts @@ -19,6 +19,7 @@ export interface CommandParser { setPreserve: (val: unknown) => unknown; } +// TODO: make multiple parsers to improve performance? export class BasicCommandParser implements CommandParser { #redisArgs: Array = []; #keys: Array = []; @@ -109,4 +110,4 @@ export class BasicCommandParser implements CommandParser { setCachable() { this.#cachable = true; }; -} \ No newline at end of file +}