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

fix #1758 - implement some CLIENT commands, add name to RedisClientOptions

This commit is contained in:
leibale
2021-12-06 21:48:14 -05:00
parent 82920aef0b
commit ec7ccaf827
14 changed files with 323 additions and 9 deletions

View File

@@ -15,6 +15,7 @@
| socket.reconnectStrategy | `retries => Math.min(retries * 50, 500)` | A function containing the [Reconnect Strategy](#reconnect-strategy) logic | | socket.reconnectStrategy | `retries => Math.min(retries * 50, 500)` | A function containing the [Reconnect Strategy](#reconnect-strategy) logic |
| username | | ACL username ([see ACL guide](https://redis.io/topics/acl)) | | username | | ACL username ([see ACL guide](https://redis.io/topics/acl)) |
| password | | ACL password or the old "--requirepass" password | | password | | ACL password or the old "--requirepass" password |
| name | | Connection name ([see `CLIENT SETNAME`](https://redis.io/commands/client-setname)) |
| database | | Database number to connect to (see [`SELECT`](https://redis.io/commands/select) command) | | database | | Database number to connect to (see [`SELECT`](https://redis.io/commands/select) command) |
| modules | | Object defining which [Redis Modules](../README.md#packages) to include | | modules | | Object defining which [Redis Modules](../README.md#packages) to include |
| scripts | | Object defining Lua Scripts to use with this client (see [Lua Scripts](../README.md#lua-scripts)) | | scripts | | Object defining Lua Scripts to use with this client (see [Lua Scripts](../README.md#lua-scripts)) |

View File

@@ -15,7 +15,12 @@ import * as ASKING from '../commands/ASKING';
import * as AUTH from '../commands/AUTH'; import * as AUTH from '../commands/AUTH';
import * as BGREWRITEAOF from '../commands/BGREWRITEAOF'; import * as BGREWRITEAOF from '../commands/BGREWRITEAOF';
import * as BGSAVE from '../commands/BGSAVE'; import * as BGSAVE from '../commands/BGSAVE';
import * as CLIENT_CACHING from '../commands/CLIENT_CACHING';
import * as CLIENT_GETNAME from '../commands/CLIENT_GETNAME';
import * as CLIENT_GETREDIR from '../commands/CLIENT_GETREDIR';
import * as CLIENT_ID from '../commands/CLIENT_ID'; import * as CLIENT_ID from '../commands/CLIENT_ID';
import * as CLIENT_KILL from '../commands/CLIENT_KILL';
import * as CLIENT_SETNAME from '../commands/CLIENT_SETNAME';
import * as CLIENT_INFO from '../commands/CLIENT_INFO'; import * as CLIENT_INFO from '../commands/CLIENT_INFO';
import * as CLUSTER_ADDSLOTS from '../commands/CLUSTER_ADDSLOTS'; import * as CLUSTER_ADDSLOTS from '../commands/CLUSTER_ADDSLOTS';
import * as CLUSTER_FLUSHSLOTS from '../commands/CLUSTER_FLUSHSLOTS'; import * as CLUSTER_FLUSHSLOTS from '../commands/CLUSTER_FLUSHSLOTS';
@@ -110,8 +115,18 @@ export default {
bgRewriteAof: BGREWRITEAOF, bgRewriteAof: BGREWRITEAOF,
BGSAVE, BGSAVE,
bgSave: BGSAVE, bgSave: BGSAVE,
CLIENT_CACHING,
clientCaching: CLIENT_CACHING,
CLIENT_GETNAME,
clientGetName: CLIENT_GETNAME,
CLIENT_GETREDIR,
clientGetRedir: CLIENT_GETREDIR,
CLIENT_ID, CLIENT_ID,
clientId: CLIENT_ID, clientId: CLIENT_ID,
CLIENT_KILL,
clientKill: CLIENT_KILL,
CLIENT_SETNAME,
clientSetName: CLIENT_SETNAME,
CLIENT_INFO, CLIENT_INFO,
clientInfo: CLIENT_INFO, clientInfo: CLIENT_INFO,
CLUSTER_ADDSLOTS, CLUSTER_ADDSLOTS,

View File

@@ -7,6 +7,7 @@ import { AbortError, ClientClosedError, ConnectionTimeoutError, DisconnectsClien
import { defineScript } from '../lua-script'; import { defineScript } from '../lua-script';
import { spy } from 'sinon'; import { spy } from 'sinon';
import { once } from 'events'; import { once } from 'events';
import { ClientKillFilters } from '../commands/CLIENT_KILL';
export const SQUARE_SCRIPT = defineScript({ export const SQUARE_SCRIPT = defineScript({
NUMBER_OF_KEYS: 0, NUMBER_OF_KEYS: 0,
@@ -125,6 +126,18 @@ describe('Client', () => {
}); });
}); });
testUtils.testWithClient('should set connection name', async client => {
assert.equal(
await client.clientGetName(),
'name'
);
}, {
...GLOBAL.SERVERS.OPEN,
clientOptions: {
name: 'name'
}
});
describe('legacyMode', () => { describe('legacyMode', () => {
function sendCommandAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, args: RedisCommandArguments): Promise<RedisCommandRawReply> { function sendCommandAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, args: RedisCommandArguments): Promise<RedisCommandRawReply> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -445,14 +458,9 @@ describe('Client', () => {
}); });
testUtils.testWithClient('executeIsolated', async client => { testUtils.testWithClient('executeIsolated', async client => {
await client.sendCommand(['CLIENT', 'SETNAME', 'client']); const id = await client.clientId(),
isolatedId = await client.executeIsolated(isolatedClient => isolatedClient.clientId());
assert.equal( assert.ok(id !== isolatedId);
await client.executeIsolated(isolatedClient =>
isolatedClient.sendCommand(['CLIENT', 'GETNAME'])
),
null
);
}, GLOBAL.SERVERS.OPEN); }, GLOBAL.SERVERS.OPEN);
async function killClient<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>): Promise<void> { async function killClient<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>): Promise<void> {
@@ -644,7 +652,10 @@ describe('Client', () => {
await Promise.all([ await Promise.all([
once(subscriber, 'error'), once(subscriber, 'error'),
publisher.sendCommand(['CLIENT', 'KILL', 'SKIPME', 'yes']) publisher.clientKill({
filter: ClientKillFilters.SKIP_ME,
skipMe: true
})
]); ]);
await once(subscriber, 'ready'); await once(subscriber, 'ready');

View File

@@ -20,6 +20,7 @@ export interface RedisClientOptions<M extends RedisModules, S extends RedisScrip
socket?: RedisSocketOptions; socket?: RedisSocketOptions;
username?: string; username?: string;
password?: string; password?: string;
name?: string;
database?: number; database?: number;
commandsQueueMaxLength?: number; commandsQueueMaxLength?: number;
readonly?: boolean; readonly?: boolean;
@@ -201,6 +202,15 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
); );
} }
if (this.#options?.name) {
promises.push(
this.#queue.addCommand(
COMMANDS.CLIENT_SETNAME.transformArguments(this.#options.name),
{ asap: true }
)
);
}
if (this.#options?.username || this.#options?.password) { if (this.#options?.username || this.#options?.password) {
promises.push( promises.push(
this.#queue.addCommand( this.#queue.addCommand(

View File

@@ -0,0 +1,20 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLIENT_CACHING';
describe('CLIENT CACHING', () => {
describe('transformArguments', () => {
it('true', () => {
assert.deepEqual(
transformArguments(true),
['CLIENT', 'CACHING', 'YES']
);
});
it('false', () => {
assert.deepEqual(
transformArguments(false),
['CLIENT', 'CACHING', 'NO']
);
});
});
});

View File

@@ -0,0 +1,11 @@
import { RedisCommandArguments } from '.';
export function transformArguments(value: boolean): RedisCommandArguments {
return [
'CLIENT',
'CACHING',
value ? 'YES' : 'NO'
];
}
export declare function transformReply(): 'OK';

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLIENT_GETNAME';
describe('CLIENT GETNAME', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLIENT', 'GETNAME']
);
});
});

View File

@@ -0,0 +1,7 @@
import { RedisCommandArguments } from '.';
export function transformArguments(): RedisCommandArguments {
return ['CLIENT', 'GETNAME'];
}
export declare function transformReply(): string | null;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLIENT_GETREDIR';
describe('CLIENT GETREDIR', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLIENT', 'GETREDIR']
);
});
});

View File

@@ -0,0 +1,7 @@
import { RedisCommandArguments } from '.';
export function transformArguments(): RedisCommandArguments {
return ['CLIENT', 'GETREDIR'];
}
export declare function transformReply(): number;

View File

@@ -0,0 +1,97 @@
import { strict as assert } from 'assert';
import { ClientKillFilters, transformArguments } from './CLIENT_KILL';
describe('CLIENT KILL', () => {
describe('transformArguments', () => {
it('ADDRESS', () => {
assert.deepEqual(
transformArguments({
filter: ClientKillFilters.ADDRESS,
address: 'ip:6379'
}),
['CLIENT', 'KILL', 'ADDR', 'ip:6379']
);
});
it('LOCAL_ADDRESS', () => {
assert.deepEqual(
transformArguments({
filter: ClientKillFilters.LOCAL_ADDRESS,
localAddress: 'ip:6379'
}),
['CLIENT', 'KILL', 'LADDR', 'ip:6379']
);
});
describe('ID', () => {
it('string', () => {
assert.deepEqual(
transformArguments({
filter: ClientKillFilters.ID,
id: '1'
}),
['CLIENT', 'KILL', 'ID', '1']
);
});
it('number', () => {
assert.deepEqual(
transformArguments({
filter: ClientKillFilters.ID,
id: 1
}),
['CLIENT', 'KILL', 'ID', '1']
);
});
});
it('TYPE', () => {
assert.deepEqual(
transformArguments({
filter: ClientKillFilters.TYPE,
type: 'master'
}),
['CLIENT', 'KILL', 'TYPE', 'master']
);
});
it('USER', () => {
assert.deepEqual(
transformArguments({
filter: ClientKillFilters.USER,
username: 'username'
}),
['CLIENT', 'KILL', 'USER', 'username']
);
});
describe('SKIP_ME', () => {
it('undefined', () => {
assert.deepEqual(
transformArguments(ClientKillFilters.SKIP_ME),
['CLIENT', 'KILL', 'SKIPME']
);
});
it('true', () => {
assert.deepEqual(
transformArguments({
filter: ClientKillFilters.SKIP_ME,
skipMe: true
}),
['CLIENT', 'KILL', 'SKIPME', 'yes']
);
});
it('false', () => {
assert.deepEqual(
transformArguments({
filter: ClientKillFilters.SKIP_ME,
skipMe: false
}),
['CLIENT', 'KILL', 'SKIPME', 'no']
);
});
});
});
});

View File

@@ -0,0 +1,95 @@
import { RedisCommandArguments } from '.';
export enum ClientKillFilters {
ADDRESS = 'ADDR',
LOCAL_ADDRESS = 'LADDR',
ID = 'ID',
TYPE = 'TYPE',
USER = 'USER',
SKIP_ME = 'SKIPME'
}
interface KillFilter<T extends ClientKillFilters> {
filter: T;
}
interface KillAddress extends KillFilter<ClientKillFilters.ADDRESS> {
address: `${string}:${number}`;
}
interface KillLocalAddress extends KillFilter<ClientKillFilters.LOCAL_ADDRESS> {
localAddress: `${string}:${number}`;
}
interface KillId extends KillFilter<ClientKillFilters.ID> {
id: number | `${number}`;
}
interface KillType extends KillFilter<ClientKillFilters.TYPE> {
type: 'normal' | 'master' | 'replica' | 'pubsub';
}
interface KillUser extends KillFilter<ClientKillFilters.USER> {
username: string;
}
type KillSkipMe = ClientKillFilters.SKIP_ME | (KillFilter<ClientKillFilters.SKIP_ME> & {
skipMe: boolean;
});
type KillFilters = KillAddress | KillLocalAddress | KillId | KillType | KillUser | KillSkipMe;
export function transformArguments(filters: KillFilters | Array<KillFilters>): RedisCommandArguments {
const args = ['CLIENT', 'KILL'];
if (Array.isArray(filters)) {
for (const filter of filters) {
pushFilter(args, filter);
}
} else {
pushFilter(args, filters);
}
return args;
}
function pushFilter(args: RedisCommandArguments, filter: KillFilters): void {
if (filter === ClientKillFilters.SKIP_ME) {
args.push('SKIPME');
return;
}
args.push(filter.filter);
switch(filter.filter) {
case ClientKillFilters.ADDRESS:
args.push(filter.address);
break;
case ClientKillFilters.LOCAL_ADDRESS:
args.push(filter.localAddress);
break;
case ClientKillFilters.ID:
args.push(
typeof filter.id === 'number' ?
filter.id.toString() :
filter.id
);
break;
case ClientKillFilters.TYPE:
args.push(filter.type);
break;
case ClientKillFilters.USER:
args.push(filter.username);
break;
case ClientKillFilters.SKIP_ME:
args.push(filter.skipMe ? 'yes' : 'no');
break;
}
}
export declare function transformReply(): number;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLIENT_SETNAME';
describe('CLIENT SETNAME', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('name'),
['CLIENT', 'SETNAME', 'name']
);
});
});

View File

@@ -0,0 +1,7 @@
import { RedisCommandArguments } from '.';
export function transformArguments(name: string): RedisCommandArguments {
return ['CLIENT', 'SETNAME', name];
}
export declare function transformReply(): string | null;