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

add HSCAN, clean some commands, add tests for generic transformers

This commit is contained in:
leibale
2021-06-29 19:58:17 -04:00
parent 22c2748fa8
commit 0feb35a1fb
14 changed files with 596 additions and 39 deletions

View File

@@ -373,13 +373,24 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
} while (cursor !== 0)
}
async* hScanIterator(key: string, options?: ScanOptions): AsyncIterable<string> {
let cursor = 0;
do {
const reply = await (this as any).hScan(key, cursor, options);
cursor = reply.cursor;
for (const key of reply.keys) {
yield key;
}
} while (cursor !== 0)
}
async* sScanIterator(key: string, options?: ScanOptions): AsyncIterable<string> {
let cursor = 0;
do {
const reply = await (this as any).sScan(key, cursor, options);
cursor = reply.cursor;
for (const key of reply.keys) {
yield key;
for (const member of reply.members) {
yield member;
}
} while (cursor !== 0)
}

View File

@@ -1,14 +1,9 @@
import { transformReplyTuples } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string): Array<string> {
return ['HGETALL', key];
}
export function transformReply(reply: Array<string>): Record<string, string> {
const obj = Object.create(null);
for (let i = 0; i < reply.length; i += 2) {
obj[reply[i]] = reply[i + 1];
}
return obj;
}
export const transformReply = transformReplyTuples;

View File

@@ -1,4 +1,4 @@
import { transformReplyString } from './generic-transformers';
import { transformReplyStringNull } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
@@ -6,4 +6,4 @@ export function transformArguments(key: string): Array<string> {
return ['HRANDFIELD', key];
}
export const transformReply = transformReplyString;
export const transformReply = transformReplyStringNull;

View File

@@ -1,4 +1,4 @@
import { transformReplyTupels, TupelsObject } from './generic-transformers';
import { transformReplyTuplesNull } from './generic-transformers';
import { transformArguments as transformHRandFieldCountArguments } from './HRANDFIELD_COUNT';
export { FIRST_KEY_INDEX } from './HRANDFIELD_COUNT';
@@ -10,8 +10,4 @@ export function transformArguments(key: string, count: number): Array<string> {
];
}
export function transformReply(reply: Array<string> | null): TupelsObject | null {
if (reply === null) return null;
return transformReplyTupels(reply);
}
export const transformReply = transformReplyTuplesNull;

View File

@@ -0,0 +1,77 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments, transformReply } from './HSCAN';
describe('HSCAN', () => {
describe('transformArguments', () => {
it('cusror only', () => {
assert.deepEqual(
transformArguments('key', 0),
['HSCAN', 'key', '0']
);
});
it('with MATCH', () => {
assert.deepEqual(
transformArguments('key', 0, {
MATCH: 'pattern'
}),
['HSCAN', 'key', '0', 'MATCH', 'pattern']
);
});
it('with COUNT', () => {
assert.deepEqual(
transformArguments('key', 0, {
COUNT: 1
}),
['HSCAN', 'key', '0', 'COUNT', '1']
);
});
it('with MATCH & COUNT', () => {
assert.deepEqual(
transformArguments('key', 0, {
MATCH: 'pattern',
COUNT: 1
}),
['HSCAN', 'key', '0', 'MATCH', 'pattern', 'COUNT', '1']
);
});
});
describe('transformReply', () => {
it('without tuples', () => {
assert.deepEqual(
transformReply(['0', []]),
{
cursor: 0,
tuples: []
}
);
});
it('with tuples', () => {
assert.deepEqual(
transformReply(['0', ['field', 'value']]),
{
cursor: 0,
tuples: [{
field: 'field',
value: 'value'
}]
}
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.hScan', async client => {
assert.deepEqual(
await client.hScan('key', 0),
{
cursor: 0,
tuples: []
}
);
});
});

36
lib/commands/HSCAN.ts Normal file
View File

@@ -0,0 +1,36 @@
import { ScanOptions, transformScanArguments } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
export function transformArguments(key: string, cursor: number, options?: ScanOptions): Array<string> {
return [
'HSCAN',
key,
...transformScanArguments(cursor, options)
];
}
interface HScanReply {
cursor: number;
tuples: Array<{
field: string;
value: string;
}>;
}
export function transformReply([cursor, rawTuples]: [string, Array<string>]): HScanReply {
const parsedTuples = [];
for (let i = 0; i < rawTuples.length; i += 2) {
parsedTuples.push({
field: rawTuples[i],
value: rawTuples[i + 1]
});
}
return {
cursor: Number(cursor),
tuples: parsedTuples
};
};

View File

@@ -1,4 +1,4 @@
import { ScanOptions, transformScanArguments, transformScanReply } from './generic-transformers';
import { ScanOptions, transformScanArguments } from './generic-transformers';
export const IS_READ_ONLY = true;
@@ -19,4 +19,14 @@ export function transformArguments(cursor: number, options?: ScanCommandOptions)
return args;
}
export const transformReply = transformScanReply;
export interface ScanReply {
cursor: number;
keys: Array<string>;
}
export function transformReply([cursor, keys]: [string, Array<string>]): ScanReply {
return {
cursor: Number(cursor),
keys
};
}

View File

@@ -0,0 +1,74 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments, transformReply } from './SSCAN';
describe('SSCAN', () => {
describe('transformArguments', () => {
it('cusror only', () => {
assert.deepEqual(
transformArguments('key', 0),
['SSCAN', 'key', '0']
);
});
it('with MATCH', () => {
assert.deepEqual(
transformArguments('key', 0, {
MATCH: 'pattern'
}),
['SSCAN', 'key', '0', 'MATCH', 'pattern']
);
});
it('with COUNT', () => {
assert.deepEqual(
transformArguments('key', 0, {
COUNT: 1
}),
['SSCAN', 'key', '0', 'COUNT', '1']
);
});
it('with MATCH & COUNT', () => {
assert.deepEqual(
transformArguments('key', 0, {
MATCH: 'pattern',
COUNT: 1
}),
['SSCAN', 'key', '0', 'MATCH', 'pattern', 'COUNT', '1']
);
});
});
describe('transformReply', () => {
it('without members', () => {
assert.deepEqual(
transformReply(['0', []]),
{
cursor: 0,
members: []
}
);
});
it('with members', () => {
assert.deepEqual(
transformReply(['0', ['member']]),
{
cursor: 0,
members: ['member']
}
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.sScan', async client => {
assert.deepEqual(
await client.sScan('key', 0),
{
cursor: 0,
members: []
}
);
});
});

View File

@@ -1,4 +1,4 @@
import { ScanOptions, transformScanArguments, transformScanReply } from './generic-transformers';
import { ScanOptions, transformScanArguments } from './generic-transformers';
export const IS_READ_ONLY = true;
@@ -10,4 +10,14 @@ export function transformArguments(key: string, cursor: number, options?: ScanOp
];
}
export const transformReply = transformScanReply;
interface SScanReply {
cursor: number;
members: Array<string>;
}
export function transformReply([cursor, members]: [string, Array<string>]): SScanReply {
return {
cursor: Number(cursor),
members
};
}

View File

@@ -0,0 +1,77 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments, transformReply } from './ZSCAN';
describe('ZSCAN', () => {
describe('transformArguments', () => {
it('cusror only', () => {
assert.deepEqual(
transformArguments('key', 0),
['ZSCAN', 'key', '0']
);
});
it('with MATCH', () => {
assert.deepEqual(
transformArguments('key', 0, {
MATCH: 'pattern'
}),
['ZSCAN', 'key', '0', 'MATCH', 'pattern']
);
});
it('with COUNT', () => {
assert.deepEqual(
transformArguments('key', 0, {
COUNT: 1
}),
['ZSCAN', 'key', '0', 'COUNT', '1']
);
});
it('with MATCH & COUNT', () => {
assert.deepEqual(
transformArguments('key', 0, {
MATCH: 'pattern',
COUNT: 1
}),
['ZSCAN', 'key', '0', 'MATCH', 'pattern', 'COUNT', '1']
);
});
});
describe('transformReply', () => {
it('without members', () => {
assert.deepEqual(
transformReply(['0', []]),
{
cursor: 0,
members: []
}
);
});
it('with members', () => {
assert.deepEqual(
transformReply(['0', ['member', '-inf']]),
{
cursor: 0,
members: [{
value: 'member',
score: -Infinity
}]
}
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.zScan', async client => {
assert.deepEqual(
await client.zScan('key', 0),
{
cursor: 0,
members: []
}
);
});
});

View File

@@ -18,7 +18,7 @@ interface ZScanReply {
}
export function transformReply([cursor, rawMembers]: [string, Array<string>]): ZScanReply {
const parsedMembers:Array<ZMember> = [];
const parsedMembers: Array<ZMember> = [];
for (let i = 0; i < rawMembers.length; i += 2) {
parsedMembers.push({
value: rawMembers[i],

View File

@@ -0,0 +1,274 @@
import { strict as assert } from 'assert';
import {
transformReplyBoolean,
transformReplyBooleanArray,
transformScanArguments,
transformReplyNumberInfinity,
transformReplyNumberInfinityArray,
transformReplyNumberInfinityNull,
transformArgumentNumberInfinity,
transformReplyTuples,
transformReplyTuplesNull,
transformReplyStreamMessages,
transformReplyStreamsMessages,
transformReplyStreamsMessagesNull,
transformReplySortedSetWithScores
} from './generic-transformers';
describe('Generic Transformers', () => {
describe('transformReplyBoolean', () => {
it('0', () => {
assert.equal(
transformReplyBoolean(0),
false
);
});
it('1', () => {
assert.equal(
transformReplyBoolean(1),
true
);
});
});
describe('transformReplyBooleanArray', () => {
it('empty array', () => {
assert.deepEqual(
transformReplyBooleanArray([]),
[]
);
});
it('0, 1', () => {
assert.deepEqual(
transformReplyBooleanArray([0, 1]),
[false, true]
);
});
});
describe('transformScanArguments', () => {
it('cusror only', () => {
assert.deepEqual(
transformScanArguments(0),
['0']
);
});
it('with MATCH', () => {
assert.deepEqual(
transformScanArguments(0, {
MATCH: 'pattern'
}),
['0', 'MATCH', 'pattern']
);
});
it('with COUNT', () => {
assert.deepEqual(
transformScanArguments(0, {
COUNT: 1
}),
['0', 'COUNT', '1']
);
});
it('with MATCH & COUNT', () => {
assert.deepEqual(
transformScanArguments(0, {
MATCH: 'pattern',
COUNT: 1
}),
['0', 'MATCH', 'pattern', 'COUNT', '1']
);
});
});
describe('transformReplyNumberInfinity', () => {
it('0.5', () => {
assert.equal(
transformReplyNumberInfinity('0.5'),
0.5
);
});
it('+inf', () => {
assert.equal(
transformReplyNumberInfinity('+inf'),
Infinity
);
});
it('-inf', () => {
assert.equal(
transformReplyNumberInfinity('-inf'),
-Infinity
);
});
});
describe('transformReplyNumberInfinityArray', () => {
it('empty array', () => {
assert.deepEqual(
transformReplyNumberInfinityArray([]),
[]
);
});
it('0.5, +inf, -inf', () => {
assert.deepEqual(
transformReplyNumberInfinityArray(['0.5', '+inf', '-inf']),
[0.5, Infinity, -Infinity]
);
});
});
it('transformReplyNumberInfinityNull', () => {
assert.equal(
transformReplyNumberInfinityNull(null),
null
);
});
describe('transformArgumentNumberInfinity', () => {
it('0.5', () => {
assert.equal(
transformArgumentNumberInfinity(0.5),
'0.5'
);
});
it('Infinity', () => {
assert.equal(
transformArgumentNumberInfinity(Infinity),
'+inf'
);
});
it('-Infinity', () => {
assert.equal(
transformArgumentNumberInfinity(-Infinity),
'-inf'
);
});
});
it('transformReplyTuples', () => {
assert.deepEqual(
transformReplyTuples(['key1', 'value1', 'key2', 'value2']),
Object.create(null, {
key1: {
value: 'value1',
configurable: true,
enumerable: true
},
key2: {
value: 'value2',
configurable: true,
enumerable: true
}
})
);
});
it('transformReplyTuplesNull', () => {
assert.equal(
transformReplyTuplesNull(null),
null
);
});
it('transformReplyStreamMessages', () => {
assert.deepEqual(
transformReplyStreamMessages(['0-0', ['0key', '0value'], '1-0', ['1key', '1value']]),
[{
id: '0-0',
message: Object.create(null, {
'0key': {
value: '0value',
configurable: true,
enumerable: true
}
})
}, {
id: '1-0',
message: Object.create(null, {
'1key': {
value: '1value',
configurable: true,
enumerable: true
}
})
}]
);
});
it('transformReplyStreamsMessages', () => {
assert.deepEqual(
transformReplyStreamsMessages([['stream1', ['0-1', ['11key', '11value'], '1-1', ['12key', '12value']]], ['stream2', ['0-2', ['2key1', '2value1', '2key2', '2value2']]]]),
[{
name: 'stream1',
messages: [{
id: '0-1',
message: Object.create(null, {
'11key': {
value: '11value',
configurable: true,
enumerable: true
}
})
}, {
id: '1-1',
message: Object.create(null, {
'12key': {
value: '12value',
configurable: true,
enumerable: true
}
})
}]
}, {
name: 'stream2',
messages: [{
id: '0-2',
message: Object.create(null, {
'2key1': {
value: '2value1',
configurable: true,
enumerable: true
},
'2key2': {
value: '2value2',
configurable: true,
enumerable: true
}
})
}]
}]
)
});
it('transformReplyStreamsMessagesNull', () => {
assert.equal(
transformReplyStreamsMessagesNull(null),
null
);
});
it('transformReplySortedSetWithScores', () => {
assert.deepEqual(
transformReplySortedSetWithScores(['member1', '0.5', 'member2', '+inf', 'member3', '-inf']),
[{
value: 'member1',
score: 0.5
}, {
value: 'member2',
score: Infinity
}, {
value: 'member3',
score: -Infinity
}]
);
});
});

View File

@@ -49,18 +49,6 @@ export function transformScanArguments(cursor: number, options?: ScanOptions): A
return args;
}
export interface ScanReply {
cursor: number;
keys: Array<string>;
}
export function transformScanReply([cursor, keys]: [string, Array<string>]): ScanReply {
return {
cursor: Number(cursor),
keys
};
}
export function transformReplyNumberInfinity(reply: string): number {
switch (reply) {
case '+inf':
@@ -101,11 +89,11 @@ export function transformArgumentNumberInfinity(num: number): string {
}
}
export interface TupelsObject {
export interface TuplesObject {
[field: string]: string;
}
export function transformReplyTupels(reply: Array<string>): TupelsObject {
export function transformReplyTuples(reply: Array<string>): TuplesObject {
const message = Object.create(null);
for (let i = 0; i < reply.length; i += 2) {
@@ -115,9 +103,15 @@ export function transformReplyTupels(reply: Array<string>): TupelsObject {
return message;
}
export function transformReplyTuplesNull(reply: Array<string> | null): TuplesObject | null {
if (reply === null) return null;
return transformReplyTuples(reply);
}
export interface StreamMessageReply {
id: string;
message: TupelsObject;
message: TuplesObject;
}
export type StreamMessagesReply = Array<StreamMessageReply>;
@@ -128,7 +122,7 @@ export function transformReplyStreamMessages(reply: Array<any>): StreamMessagesR
for (let i = 0; i < reply.length; i += 2) {
messages.push({
id: reply[i],
message: transformReplyTupels(reply[i + 1])
message: transformReplyTuples(reply[i + 1])
});
}

View File

@@ -38,6 +38,7 @@ import * as HMGET from './HMGET';
import * as HRANDFIELD_COUNT_WITHVALUES from './HRANDFIELD_COUNT_WITHVALUES';
import * as HRANDFIELD_COUNT from './HRANDFIELD_COUNT';
import * as HRANDFIELD from './HRANDFIELD';
import * as HSCAN from './HSCAN';
import * as HSET from './HSET';
import * as HSETNX from './HSETNX';
import * as HSTRLEN from './HSTRLEN';
@@ -244,6 +245,8 @@ export default {
hRandFieldCount: HRANDFIELD_COUNT,
HRANDFIELD,
hRandField: HRANDFIELD,
HSCAN,
hScan: HSCAN,
HSET,
hSet: HSET,
HSETNX,