1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +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

@@ -2,7 +2,7 @@ import { strict as assert } from 'assert';
import testUtils, { GLOBAL, waitTillBeenCalled } from '../test-utils';
import RedisClient, { RedisClientType } from '.';
import { RedisClientMultiCommandType } from './multi-command';
import { RedisCommandArguments, RedisCommandRawReply, RedisModules, RedisScripts } from '../commands';
import { RedisCommandArguments, RedisCommandRawReply, RedisModules, RedisFunctions, RedisScripts } from '../commands';
import { AbortError, ClientClosedError, ConnectionTimeoutError, DisconnectsClientError, SocketClosedUnexpectedlyError, WatchError } from '../errors';
import { defineScript } from '../lua-script';
import { spy } from 'sinon';
@@ -10,16 +10,42 @@ import { once } from 'events';
import { ClientKillFilters } from '../commands/CLIENT_KILL';
export const SQUARE_SCRIPT = defineScript({
NUMBER_OF_KEYS: 0,
SCRIPT: 'return ARGV[1] * ARGV[1];',
NUMBER_OF_KEYS: 0,
transformArguments(number: number): Array<string> {
return [number.toString()];
},
transformReply(reply: number): number {
return reply;
}
});
export const MATH_FUNCTION = {
name: 'math',
engine: 'LUA',
code: `#!LUA name=math
redis.register_function{
function_name = "square",
callback = function(keys, args) return args[1] * args[1] end,
flags = { "no-writes" }
}`,
library: {
square: {
NAME: 'square',
NUMBER_OF_KEYS: 0,
transformArguments(number: number): Array<string> {
return [number.toString()];
}
}
}
};
export async function loadMathFunction(
client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>
): Promise<void> {
await client.functionLoad(
MATH_FUNCTION.code,
{ REPLACE: true }
);
}
describe('Client', () => {
describe('parseURL', () => {
it('redis://user:secret@localhost:6379/0', () => {
@@ -115,7 +141,14 @@ describe('Client', () => {
});
describe('legacyMode', () => {
function sendCommandAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, args: RedisCommandArguments): Promise<RedisCommandRawReply> {
function sendCommandAsync<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
>(
client: RedisClientType<M, F, S>,
args: RedisCommandArguments
): Promise<RedisCommandRawReply> {
return new Promise((resolve, reject) => {
(client as any).sendCommand(args, (err: Error | undefined, reply: RedisCommandRawReply) => {
if (err) return reject(err);
@@ -159,7 +192,14 @@ describe('Client', () => {
}
});
function setAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, ...args: Array<any>): Promise<RedisCommandRawReply> {
function setAsync<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
>(
client: RedisClientType<M, F, S>,
...args: Array<any>
): Promise<RedisCommandRawReply> {
return new Promise((resolve, reject) => {
(client as any).set(...args, (err: Error | undefined, reply: RedisCommandRawReply) => {
if (err) return reject(err);
@@ -205,7 +245,11 @@ describe('Client', () => {
}
});
function multiExecAsync<M extends RedisModules, S extends RedisScripts>(multi: RedisClientMultiCommandType<M, S>): Promise<Array<RedisCommandRawReply>> {
function multiExecAsync<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
>(multi: RedisClientMultiCommandType<M, F, S>): Promise<Array<RedisCommandRawReply>> {
return new Promise((resolve, reject) => {
(multi as any).exec((err: Error | undefined, replies: Array<RedisCommandRawReply>) => {
if (err) return reject(err);
@@ -439,25 +483,44 @@ describe('Client', () => {
}
});
const module = {
echo: {
transformArguments(message: string): Array<string> {
return ['ECHO', message];
},
transformReply(reply: string): string {
return reply;
}
}
};
testUtils.testWithClient('modules', async client => {
// assert.equal(
// await client.module.echo('message'),
// 'message'
// );
assert.equal(
await client.module.echo('message'),
'message'
);
}, {
...GLOBAL.SERVERS.OPEN,
clientOptions: {
modules: {
module: {
echo: {
transformArguments(message: string): Array<string> {
return ['ECHO', message];
},
transformReply(reply: string): string {
return reply;
}
}
}
module
}
}
});
testUtils.testWithClient('functions', async client => {
await loadMathFunction(client);
assert.equal(
await client.math.square(2),
4
);
}, {
...GLOBAL.SERVERS.OPEN,
minimumDockerVersion: [7, 0],
clientOptions: {
functions: {
math: MATH_FUNCTION.library
}
}
});
@@ -468,9 +531,13 @@ describe('Client', () => {
assert.ok(id !== isolatedId);
}, GLOBAL.SERVERS.OPEN);
async function killClient<M extends RedisModules, S extends RedisScripts>(
client: RedisClientType<M, S>,
errorClient: RedisClientType<M, S> = client
async function killClient<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
>(
client: RedisClientType<M, F, S>,
errorClient: RedisClientType<M, F, S> = client
): Promise<void> {
const onceErrorPromise = once(errorClient, 'error');
await client.sendCommand(['QUIT']);
@@ -684,7 +751,9 @@ describe('Client', () => {
try {
await assert.doesNotReject(Promise.all([
subscriber.subscribe('channel', () => {}),
subscriber.subscribe('channel', () => {
// noop
}),
publisher.publish('channel', 'message')
]));
} finally {