Skip to content

Commit

Permalink
fix #1758 - implement some CLIENT commands, add name to `RedisClien…
Browse files Browse the repository at this point in the history
…tOptions`
  • Loading branch information
leibale committed Dec 7, 2021
1 parent 82920ae commit ec7ccaf
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/client-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
| 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)) |
| 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) |
| 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)) |
Expand Down
15 changes: 15 additions & 0 deletions packages/client/lib/client/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import * as ASKING from '../commands/ASKING';
import * as AUTH from '../commands/AUTH';
import * as BGREWRITEAOF from '../commands/BGREWRITEAOF';
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_KILL from '../commands/CLIENT_KILL';
import * as CLIENT_SETNAME from '../commands/CLIENT_SETNAME';
import * as CLIENT_INFO from '../commands/CLIENT_INFO';
import * as CLUSTER_ADDSLOTS from '../commands/CLUSTER_ADDSLOTS';
import * as CLUSTER_FLUSHSLOTS from '../commands/CLUSTER_FLUSHSLOTS';
Expand Down Expand Up @@ -110,8 +115,18 @@ export default {
bgRewriteAof: BGREWRITEAOF,
BGSAVE,
bgSave: BGSAVE,
CLIENT_CACHING,
clientCaching: CLIENT_CACHING,
CLIENT_GETNAME,
clientGetName: CLIENT_GETNAME,
CLIENT_GETREDIR,
clientGetRedir: CLIENT_GETREDIR,
CLIENT_ID,
clientId: CLIENT_ID,
CLIENT_KILL,
clientKill: CLIENT_KILL,
CLIENT_SETNAME,
clientSetName: CLIENT_SETNAME,
CLIENT_INFO,
clientInfo: CLIENT_INFO,
CLUSTER_ADDSLOTS,
Expand Down
29 changes: 20 additions & 9 deletions packages/client/lib/client/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AbortError, ClientClosedError, ConnectionTimeoutError, DisconnectsClien
import { defineScript } from '../lua-script';
import { spy } from 'sinon';
import { once } from 'events';
import { ClientKillFilters } from '../commands/CLIENT_KILL';

export const SQUARE_SCRIPT = defineScript({
NUMBER_OF_KEYS: 0,
Expand Down Expand Up @@ -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', () => {
function sendCommandAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, args: RedisCommandArguments): Promise<RedisCommandRawReply> {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -445,14 +458,9 @@ describe('Client', () => {
});

testUtils.testWithClient('executeIsolated', async client => {
await client.sendCommand(['CLIENT', 'SETNAME', 'client']);

assert.equal(
await client.executeIsolated(isolatedClient =>
isolatedClient.sendCommand(['CLIENT', 'GETNAME'])
),
null
);
const id = await client.clientId(),
isolatedId = await client.executeIsolated(isolatedClient => isolatedClient.clientId());
assert.ok(id !== isolatedId);
}, GLOBAL.SERVERS.OPEN);

async function killClient<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>): Promise<void> {
Expand Down Expand Up @@ -644,7 +652,10 @@ describe('Client', () => {

await Promise.all([
once(subscriber, 'error'),
publisher.sendCommand(['CLIENT', 'KILL', 'SKIPME', 'yes'])
publisher.clientKill({
filter: ClientKillFilters.SKIP_ME,
skipMe: true
})
]);

await once(subscriber, 'ready');
Expand Down
10 changes: 10 additions & 0 deletions packages/client/lib/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface RedisClientOptions<M extends RedisModules, S extends RedisScrip
socket?: RedisSocketOptions;
username?: string;
password?: string;
name?: string;
database?: number;
commandsQueueMaxLength?: number;
readonly?: boolean;
Expand Down Expand Up @@ -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) {
promises.push(
this.#queue.addCommand(
Expand Down
20 changes: 20 additions & 0 deletions packages/client/lib/commands/CLIENT_CACHING.spec.ts
Original file line number Diff line number Diff line change
@@ -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']
);
});
});
});
11 changes: 11 additions & 0 deletions packages/client/lib/commands/CLIENT_CACHING.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { RedisCommandArguments } from '.';

export function transformArguments(value: boolean): RedisCommandArguments {
return [
'CLIENT',
'CACHING',
value ? 'YES' : 'NO'
];
}

export declare function transformReply(): 'OK';
11 changes: 11 additions & 0 deletions packages/client/lib/commands/CLIENT_GETNAME.spec.ts
Original file line number Diff line number Diff line change
@@ -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']
);
});
});
7 changes: 7 additions & 0 deletions packages/client/lib/commands/CLIENT_GETNAME.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { RedisCommandArguments } from '.';

export function transformArguments(): RedisCommandArguments {
return ['CLIENT', 'GETNAME'];
}

export declare function transformReply(): string | null;
11 changes: 11 additions & 0 deletions packages/client/lib/commands/CLIENT_GETREDIR.spec.ts
Original file line number Diff line number Diff line change
@@ -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']
);
});
});
7 changes: 7 additions & 0 deletions packages/client/lib/commands/CLIENT_GETREDIR.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { RedisCommandArguments } from '.';

export function transformArguments(): RedisCommandArguments {
return ['CLIENT', 'GETREDIR'];
}

export declare function transformReply(): number;
97 changes: 97 additions & 0 deletions packages/client/lib/commands/CLIENT_KILL.spec.ts
Original file line number Diff line number Diff line change
@@ -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']
);
});
});
});
});
95 changes: 95 additions & 0 deletions packages/client/lib/commands/CLIENT_KILL.ts
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions packages/client/lib/commands/CLIENT_SETNAME.spec.ts
Original file line number Diff line number Diff line change
@@ -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']
);
});
});

0 comments on commit ec7ccaf

Please sign in to comment.