1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-04 15:02:09 +03:00

Add support for redis functions (#2020)

* fix #1906 - implement BITFIELD_RO

* initial support for redis functions

* fix test utils

* redis functions commands and tests

* upgrade deps

* fix "Property 'uninstall' does not exist on type 'SinonFakeTimers'"

* upgrade dockers version

* Merge branch 'master' of github.com:redis/node-redis into functions

* fix FUNCTION LIST WITHCODE and FUNCTION STATS

* upgrade deps

* set minimum version for FCALL and FCALL_RO

* fix FUNCTION LOAD

* FUNCTION LOAD

* fix FUNCTION LOAD & FUNCTION LIST & FUNCTION LOAD WITHCODE

* fix FUNCTION_LIST_WITHCODE test
This commit is contained in:
Leibale Eidelman
2022-04-25 09:09:23 -04:00
committed by GitHub
parent 23b65133c9
commit 11c6c24881
51 changed files with 1406 additions and 324 deletions

View File

@@ -1,18 +1,22 @@
import COMMANDS from './commands';
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
import { ClientCommandOptions, RedisClientCommandSignature, RedisClientOptions, RedisClientType, WithModules, WithScripts } from '../client';
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, RedisCommandSignature, RedisFunction } from '../commands';
import { ClientCommandOptions, RedisClientOptions, RedisClientType, WithFunctions, WithModules, WithScripts } from '../client';
import RedisClusterSlots, { ClusterNode, NodeAddressMap } from './cluster-slots';
import { extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, extendWithCommands } from '../commander';
import { attachExtensions, transformCommandReply, attachCommands, transformCommandArguments } from '../commander';
import { EventEmitter } from 'events';
import RedisClusterMultiCommand, { RedisClusterMultiCommandType } from './multi-command';
import RedisClusterMultiCommand, { InstantiableRedisClusterMultiCommandType, RedisClusterMultiCommandType } from './multi-command';
import { RedisMultiQueuedCommand } from '../multi-command';
export type RedisClusterClientOptions = Omit<RedisClientOptions, 'modules' | 'scripts'>;
export type RedisClusterClientOptions = Omit<
RedisClientOptions,
'modules' | 'functions' | 'scripts' | 'database'
>;
export interface RedisClusterOptions<
M extends RedisModules = Record<string, never>,
F extends RedisFunctions = Record<string, never>,
S extends RedisScripts = Record<string, never>
> extends RedisPlugins<M, S> {
> extends RedisExtensions<M, F, S> {
rootNodes: Array<RedisClusterClientOptions>;
defaults?: Partial<RedisClusterClientOptions>;
useReplicas?: boolean;
@@ -21,16 +25,25 @@ export interface RedisClusterOptions<
}
type WithCommands = {
[P in keyof typeof COMMANDS]: RedisClientCommandSignature<(typeof COMMANDS)[P]>;
[P in keyof typeof COMMANDS]: RedisCommandSignature<(typeof COMMANDS)[P]>;
};
export type RedisClusterType<
M extends RedisModules = Record<string, never>,
F extends RedisFunctions = Record<string, never>,
S extends RedisScripts = Record<string, never>
> = RedisCluster<M, S> & WithCommands & WithModules<M> & WithScripts<S>;
> = RedisCluster<M, F, S> & WithCommands & WithModules<M> & WithFunctions<F> & WithScripts<S>;
export default class RedisCluster<M extends RedisModules, S extends RedisScripts> extends EventEmitter {
static extractFirstKey(command: RedisCommand, originalArgs: Array<unknown>, redisArgs: RedisCommandArguments): RedisCommandArgument | undefined {
export default class RedisCluster<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> extends EventEmitter {
static extractFirstKey(
command: RedisCommand,
originalArgs: Array<unknown>,
redisArgs: RedisCommandArguments
): RedisCommandArgument | undefined {
if (command.FIRST_KEY_INDEX === undefined) {
return undefined;
} else if (typeof command.FIRST_KEY_INDEX === 'number') {
@@ -40,21 +53,27 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
return command.FIRST_KEY_INDEX(...originalArgs);
}
static create<M extends RedisModules, S extends RedisScripts>(options?: RedisClusterOptions<M, S>): RedisClusterType<M, S> {
return new (<any>extendWithModulesAndScripts({
static create<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
>(options?: RedisClusterOptions<M, F, S>): RedisClusterType<M, F, S> {
return new (attachExtensions({
BaseClass: RedisCluster,
modulesExecutor: RedisCluster.prototype.commandsExecutor,
modules: options?.modules,
modulesCommandsExecutor: RedisCluster.prototype.commandsExecutor,
scripts: options?.scripts,
scriptsExecutor: RedisCluster.prototype.scriptsExecutor
functionsExecutor: RedisCluster.prototype.functionsExecutor,
functions: options?.functions,
scriptsExecutor: RedisCluster.prototype.scriptsExecutor,
scripts: options?.scripts
}))(options);
}
readonly #options: RedisClusterOptions<M, S>;
readonly #slots: RedisClusterSlots<M, S>;
readonly #Multi: new (...args: ConstructorParameters<typeof RedisClusterMultiCommand>) => RedisClusterMultiCommandType<M, S>;
readonly #options: RedisClusterOptions<M, F, S>;
readonly #slots: RedisClusterSlots<M, F, S>;
readonly #Multi: InstantiableRedisClusterMultiCommandType<M, F, S>;
constructor(options: RedisClusterOptions<M, S>) {
constructor(options: RedisClusterOptions<M, F, S>) {
super();
this.#options = options;
@@ -62,7 +81,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
this.#Multi = RedisClusterMultiCommand.extend(options);
}
duplicate(overrides?: Partial<RedisClusterOptions<M, S>>): RedisClusterType<M, S> {
duplicate(overrides?: Partial<RedisClusterOptions<M, F, S>>): RedisClusterType<M, F, S> {
return new (Object.getPrototypeOf(this).constructor)({
...this.#options,
...overrides
@@ -73,9 +92,11 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
return this.#slots.connect();
}
async commandsExecutor(command: RedisCommand, args: Array<unknown>): Promise<RedisCommandReply<typeof command>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(command, args);
async commandsExecutor<C extends RedisCommand>(
command: C,
args: Array<unknown>
): Promise<RedisCommandReply<C>> {
const { args: redisArgs, options } = transformCommandArguments(command, args);
return transformCommandReply(
command,
await this.sendCommand(
@@ -101,9 +122,38 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
);
}
async scriptsExecutor(script: RedisScript, args: Array<unknown>): Promise<RedisCommandReply<typeof script>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(script, args);
async functionsExecutor<F extends RedisFunction>(
fn: F,
args: Array<unknown>
): Promise<RedisCommandReply<F>> {
const { args: redisArgs, options } = transformCommandArguments(fn, args);
return transformCommandReply(
fn,
await this.executeFunction(
fn,
args,
redisArgs,
options
),
redisArgs.preserve
);
}
async executeFunction(
fn: RedisFunction,
originalArgs: Array<unknown>,
redisArgs: RedisCommandArguments,
options?: ClientCommandOptions
): Promise<RedisCommandRawReply> {
return this.#execute(
RedisCluster.extractFirstKey(fn, originalArgs, redisArgs),
fn.IS_READ_ONLY,
client => client.executeFunction(fn, redisArgs, options)
);
}
async scriptsExecutor<S extends RedisScript>(script: S, args: Array<unknown>): Promise<RedisCommandReply<S>> {
const { args: redisArgs, options } = transformCommandArguments(script, args);
return transformCommandReply(
script,
await this.executeScript(
@@ -121,7 +171,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
originalArgs: Array<unknown>,
redisArgs: RedisCommandArguments,
options?: ClientCommandOptions
): Promise<RedisCommandReply<typeof script>> {
): Promise<RedisCommandRawReply> {
return this.#execute(
RedisCluster.extractFirstKey(script, originalArgs, redisArgs),
script.IS_READ_ONLY,
@@ -132,7 +182,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
async #execute<Reply>(
firstKey: RedisCommandArgument | undefined,
isReadonly: boolean | undefined,
executor: (client: RedisClientType<M, S>) => Promise<Reply>
executor: (client: RedisClientType<M, F, S>) => Promise<Reply>
): Promise<Reply> {
const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16;
let client = this.#slots.getClient(firstKey, isReadonly);
@@ -171,7 +221,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
}
}
multi(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, S> {
multi(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, F, S> {
return new this.#Multi(
(commands: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => {
return this.#execute(
@@ -184,11 +234,11 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
);
}
getMasters(): Array<ClusterNode<M, S>> {
getMasters(): Array<ClusterNode<M, F, S>> {
return this.#slots.getMasters();
}
getSlotMaster(slot: number): ClusterNode<M, S> {
getSlotMaster(slot: number): ClusterNode<M, F, S> {
return this.#slots.getSlotMaster(slot);
}
@@ -201,7 +251,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
}
}
extendWithCommands({
attachCommands({
BaseClass: RedisCluster,
commands: COMMANDS,
executor: RedisCluster.prototype.commandsExecutor