diff --git a/docs/v4-to-v5.md b/docs/v4-to-v5.md index 95c2230ce23..7ce82a45761 100644 --- a/docs/v4-to-v5.md +++ b/docs/v4-to-v5.md @@ -106,6 +106,10 @@ await pool.ping(); See the [pool guide](./pool.md) for more information. +## Cluster 'PING' + +in v4, `cluster.ping()` would send the ping command to a random node, in v5, this command is no longer available to the cluster client + ## Cluster `MULTI` In v4, `cluster.multi()` did not support executing commands on replicas, even if they were readonly. diff --git a/packages/client/lib/commands/HEXPIRE.spec.ts b/packages/client/lib/commands/HEXPIRE.spec.ts new file mode 100644 index 00000000000..d5440b0796d --- /dev/null +++ b/packages/client/lib/commands/HEXPIRE.spec.ts @@ -0,0 +1,40 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HEXPIRE from './HEXPIRE'; + +describe('HEXPIRE', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HEXPIRE.transformArguments('key', 'field', 1), + ['HEXPIRE', 'key', '1', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HEXPIRE.transformArguments('key', ['field1', 'field2'], 1), + ['HEXPIRE', 'key', '1', '2', 'field1', 'field2'] + ); + }); + + it('with set option', () => { + assert.deepEqual( + HEXPIRE.transformArguments('key', 'field1', 1, 'NX'), + ['HEXPIRE', 'key', '1', 'NX', '1', 'field1'] + ); + }); + }); + + testUtils.testAll('hexpire', async client => { + assert.equal( + await client.hExpire('key', ['field1'], 0), + null, + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HEXPIRE.ts b/packages/client/lib/commands/HEXPIRE.ts new file mode 100644 index 00000000000..c33348c4e80 --- /dev/null +++ b/packages/client/lib/commands/HEXPIRE.ts @@ -0,0 +1,42 @@ +import { Command, NullReply, RedisArgument } from "../RESP/types"; +import { RedisVariadicArgument, pushVariadicArgument } from "./generic-transformers"; + +/** + * @readonly + * @enum {number} + */ +export const HASH_EXPIRATION = { + /** @property {number} */ + /** The field does not exist */ + FieldNotExists: -2, + /** @property {number} */ + /** Specified NX | XX | GT | LT condition not met */ + ConditionNotMet: 0, + /** @property {number} */ + /** Expiration time was set or updated */ + Updated: 1, + /** @property {number} */ + /** Field deleted because the specified expiration time is in the past */ + Deleted: 2 +} as const; + +export type HashExpiration = typeof HASH_EXPIRATION[keyof typeof HASH_EXPIRATION]; + +export default { + FIRST_KEY_INDEX: 1, + transformArguments( + key: RedisArgument, + fields: RedisVariadicArgument, + seconds: number, + mode?: 'NX' | 'XX' | 'GT' | 'LT', + ) { + const args = ['HEXPIRE', key, seconds.toString()]; + + if (mode) { + args.push(mode); + } + + return pushVariadicArgument(args, fields); + }, + transformReply: undefined as unknown as () => NullReply | Array<HashExpiration> +} as const satisfies Command; \ No newline at end of file diff --git a/packages/client/lib/commands/HEXPIREAT.spec.ts b/packages/client/lib/commands/HEXPIREAT.spec.ts new file mode 100644 index 00000000000..47bc66eafe6 --- /dev/null +++ b/packages/client/lib/commands/HEXPIREAT.spec.ts @@ -0,0 +1,48 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HEXPIREAT from './HEXPIREAT'; + +describe('HEXPIREAT', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string + number', () => { + assert.deepEqual( + HEXPIREAT.transformArguments('key', 'field', 1), + ['HEXPIREAT', 'key', '1', '1', 'field'] + ); + }); + + it('array + number', () => { + assert.deepEqual( + HEXPIREAT.transformArguments('key', ['field1', 'field2'], 1), + ['HEXPIREAT', 'key', '1', '2', 'field1', 'field2'] + ); + }); + + it('date', () => { + const d = new Date(); + assert.deepEqual( + HEXPIREAT.transformArguments('key', ['field1'], d), + ['HEXPIREAT', 'key', Math.floor(d.getTime() / 1000).toString(), '1', 'field1'] + ); + }); + + it('with set option', () => { + assert.deepEqual( + HEXPIREAT.transformArguments('key', 'field1', 1, 'GT'), + ['HEXPIREAT', 'key', '1', 'GT', 1, 'field1'] + ); + }); + }); + + testUtils.testAll('expireAt', async client => { + assert.equal( + await client.hExpireAt('key', 'field1', 1), + null + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HEXPIREAT.ts b/packages/client/lib/commands/HEXPIREAT.ts new file mode 100644 index 00000000000..eccc2a1978b --- /dev/null +++ b/packages/client/lib/commands/HEXPIREAT.ts @@ -0,0 +1,23 @@ +import { RedisArgument, Command, NullReply } from '../RESP/types'; +import { HashExpiration } from './HEXPIRE'; +import { RedisVariadicArgument, pushVariadicArgument, transformEXAT } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments( + key: RedisArgument, + fields: RedisVariadicArgument, + timestamp: number | Date, + mode?: 'NX' | 'XX' | 'GT' | 'LT' + ) { + const args = ['HEXPIREAT', key, transformEXAT(timestamp)]; + + if (mode) { + args.push(mode); + } + + return pushVariadicArgument(args, fields); + }, + transformReply: undefined as unknown as () => NullReply | Array<HashExpiration> +} as const satisfies Command; diff --git a/packages/client/lib/commands/HEXPIRETIME.spec.ts b/packages/client/lib/commands/HEXPIRETIME.spec.ts new file mode 100644 index 00000000000..9bfb88f4dda --- /dev/null +++ b/packages/client/lib/commands/HEXPIRETIME.spec.ts @@ -0,0 +1,33 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HEXPIRETIME from './HEXPIRETIME'; + +describe('HEXPIRETIME', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HEXPIRETIME.transformArguments('key', 'field'), + ['HEXPIRETIME', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HEXPIRETIME.transformArguments('key', ['field1', 'field2']), + ['HEXPIRETIME', 'key', '2', 'field1', 'field2'] + ); + }); + }) + + testUtils.testAll('hExpireTime', async client => { + assert.equal( + await client.hExpireTime('key', 'field1'), + null + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HEXPIRETIME.ts b/packages/client/lib/commands/HEXPIRETIME.ts new file mode 100644 index 00000000000..0184ebeaa8f --- /dev/null +++ b/packages/client/lib/commands/HEXPIRETIME.ts @@ -0,0 +1,20 @@ +import { RedisArgument, NumberReply, Command, NullReply, ArrayReply } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export const HASH_EXPIRATION_TIME = { + /** @property {number} */ + /** The field does not exist */ + FieldNotExists: -2, + /** @property {number} */ + /** The field exists but has no associated expire */ + NoExpiration: -1, +} as const; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['HEXPIRETIME', key], fields); + }, + transformReply: undefined as unknown as () => NullReply | ArrayReply<NumberReply> +} as const satisfies Command; \ No newline at end of file diff --git a/packages/client/lib/commands/HPERSIST.spec.ts b/packages/client/lib/commands/HPERSIST.spec.ts new file mode 100644 index 00000000000..fb7f375c0b2 --- /dev/null +++ b/packages/client/lib/commands/HPERSIST.spec.ts @@ -0,0 +1,33 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPERSIST from './HPERSIST'; + +describe('PERSIST', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HPERSIST.transformArguments('key', 'field'), + ['PERSIST', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HPERSIST.transformArguments('key', ['field1', 'field2']), + ['PERSIST', 'key', '2', 'field1', 'field2'] + ); + }); + }) + + testUtils.testAll('hPersist', async client => { + assert.equal( + await client.hPersist('key', 'field1'), + null + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPERSIST.ts b/packages/client/lib/commands/HPERSIST.ts new file mode 100644 index 00000000000..8f75e80aa96 --- /dev/null +++ b/packages/client/lib/commands/HPERSIST.ts @@ -0,0 +1,10 @@ +import { RedisArgument, NumberReply, Command, NullReply, ArrayReply } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['PERSIST', key], fields); + }, + transformReply: undefined as unknown as () => NullReply | ArrayReply<NumberReply> +} as const satisfies Command; diff --git a/packages/client/lib/commands/HPEXPIRE.spec.ts b/packages/client/lib/commands/HPEXPIRE.spec.ts new file mode 100644 index 00000000000..9dcea824cb8 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIRE.spec.ts @@ -0,0 +1,40 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPEXPIRE from './HPEXPIRE'; + +describe('HEXPIRE', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HPEXPIRE.transformArguments('key', 'field', 1), + ['HPEXPIRE', 'key', '1', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HPEXPIRE.transformArguments('key', ['field1', 'field2'], 1), + ['HPEXPIRE', 'key', '1', '2', 'field1', 'field2'] + ); + }); + + it('with set option', () => { + assert.deepEqual( + HPEXPIRE.transformArguments('key', ['field1'], 1, 'NX'), + ['HPEXPIRE', 'key', '1', 'NX', '1', 'field1'] + ); + }); + }); + + testUtils.testAll('hexpire', async client => { + assert.equal( + await client.hpExpire('key', ['field1'], 0), + null, + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPEXPIRE.ts b/packages/client/lib/commands/HPEXPIRE.ts new file mode 100644 index 00000000000..c20b1be4b34 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIRE.ts @@ -0,0 +1,22 @@ +import { Command, NullReply, RedisArgument } from "../RESP/types"; +import { HashExpiration } from "./HEXPIRE"; +import { RedisVariadicArgument, pushVariadicArgument } from "./generic-transformers"; + +export default { + FIRST_KEY_INDEX: 1, + transformArguments( + key: RedisArgument, + fields: RedisVariadicArgument, + ms: number, + mode?: 'NX' | 'XX' | 'GT' | 'LT', + ) { + const args = ['HPEXPIRE', key, ms.toString()]; + + if (mode) { + args.push(mode); + } + + return pushVariadicArgument(args, fields); + }, + transformReply: undefined as unknown as () => NullReply | Array<HashExpiration> +} as const satisfies Command; \ No newline at end of file diff --git a/packages/client/lib/commands/HPEXPIREAT.spec.ts b/packages/client/lib/commands/HPEXPIREAT.spec.ts new file mode 100644 index 00000000000..1b48dbb3711 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIREAT.spec.ts @@ -0,0 +1,48 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPEXPIREAT from './HPEXPIREAT'; + +describe('HPEXPIREAT', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string + number', () => { + assert.deepEqual( + HPEXPIREAT.transformArguments('key', 'field', 1), + ['HPEXPIREAT', 'key', '1', '1', 'field'] + ); + }); + + it('array + number', () => { + assert.deepEqual( + HPEXPIREAT.transformArguments('key', ['field1', 'field2'], 1), + ['HPEXPIREAT', 'key', '1', '2', 'field1', 'field2'] + ); + }); + + it('date', () => { + const d = new Date(); + assert.deepEqual( + HPEXPIREAT.transformArguments('key', ['field1'], d), + ['HPEXPIREAT', 'key', d.getTime().toString(), '1', 'field1'] + ); + }); + + it('with set option', () => { + assert.deepEqual( + HPEXPIREAT.transformArguments('key', ['field1'], 1, 'XX'), + ['HPEXPIREAT', 'key', '1', 'XX', '1', 'field1'] + ); + }); + }); + + testUtils.testAll('hpExpireAt', async client => { + assert.equal( + await client.hpExpireAt('key', ['field1'], 1), + null, + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPEXPIREAT.ts b/packages/client/lib/commands/HPEXPIREAT.ts new file mode 100644 index 00000000000..ca3cb95dfa4 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIREAT.ts @@ -0,0 +1,23 @@ +import { RedisArgument, Command, NullReply } from '../RESP/types'; +import { HashExpiration } from './HEXPIRE'; +import { RedisVariadicArgument, pushVariadicArgument, transformEXAT } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments( + key: RedisArgument, + fields: RedisVariadicArgument, + timestamp: number | Date, + mode?: 'NX' | 'XX' | 'GT' | 'LT' + ) { + const args = ['HPEXPIREAT', key, transformEXAT(timestamp)]; + + if (mode) { + args.push(mode); + } + + return pushVariadicArgument(args, fields); + }, + transformReply: undefined as unknown as () => NullReply | Array<HashExpiration> +} as const satisfies Command; diff --git a/packages/client/lib/commands/HPEXPIRETIME.spec.ts b/packages/client/lib/commands/HPEXPIRETIME.spec.ts new file mode 100644 index 00000000000..e2b88b72802 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIRETIME.spec.ts @@ -0,0 +1,33 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPEXPIRETIME from './HPEXPIRETIME'; + +describe('HPEXPIRETIME', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HPEXPIRETIME.transformArguments('key', 'field'), + ['HPEXPIRETIME', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HPEXPIRETIME.transformArguments('key', ['field1', 'field2']), + ['HPEXPIRETIME', 'key', '2', 'field1', 'field2'] + ); + }); + }); + + testUtils.testAll('hpExpireTime', async client => { + assert.equal( + await client.hpExpireTime('key', 'field1'), + null + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPEXPIRETIME.ts b/packages/client/lib/commands/HPEXPIRETIME.ts new file mode 100644 index 00000000000..db0d30f3136 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIRETIME.ts @@ -0,0 +1,11 @@ +import { RedisArgument, NumberReply, Command, NullReply, ArrayReply } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['HPEXPIRETIME', key], fields); + }, + transformReply: undefined as unknown as () => NullReply | ArrayReply<NumberReply> +} as const satisfies Command; \ No newline at end of file diff --git a/packages/client/lib/commands/HPTTL.spec.ts b/packages/client/lib/commands/HPTTL.spec.ts new file mode 100644 index 00000000000..30439aebe6a --- /dev/null +++ b/packages/client/lib/commands/HPTTL.spec.ts @@ -0,0 +1,33 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPTTL from './HPTTL'; + +describe('HPTTL', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HPTTL.transformArguments('key', 'field'), + ['PTTL', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HPTTL.transformArguments('key', ['field1', 'field2']), + ['PTTL', 'key', '2', 'field1', 'field2'] + ); + }); + }); + + testUtils.testAll('hpTTL', async client => { + assert.equal( + await client.hpTTL('key', 'field1'), + null + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPTTL.ts b/packages/client/lib/commands/HPTTL.ts new file mode 100644 index 00000000000..8f09bf5ba6d --- /dev/null +++ b/packages/client/lib/commands/HPTTL.ts @@ -0,0 +1,11 @@ +import { RedisArgument, NumberReply, Command, NullReply, ArrayReply } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['HPTTL', key], fields); + }, + transformReply: undefined as unknown as () => NullReply | ArrayReply<NumberReply> +} as const satisfies Command; diff --git a/packages/client/lib/commands/HTTL.spec.ts b/packages/client/lib/commands/HTTL.spec.ts new file mode 100644 index 00000000000..454e98d9287 --- /dev/null +++ b/packages/client/lib/commands/HTTL.spec.ts @@ -0,0 +1,34 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HTTL from './HTTL'; + +describe('HTTL', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HTTL.transformArguments('key', 'field'), + ['TTL', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HTTL.transformArguments('key', ['field1', 'field2']), + ['TTL', 'key', '2', 'field1', 'field2'] + ); + }); + + }); + + testUtils.testAll('hTTL', async client => { + assert.equal( + await client.hTTL('key', 'field1'), + null + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HTTL.ts b/packages/client/lib/commands/HTTL.ts new file mode 100644 index 00000000000..a1602729415 --- /dev/null +++ b/packages/client/lib/commands/HTTL.ts @@ -0,0 +1,11 @@ +import { RedisArgument, NumberReply, Command, NullReply, ArrayReply } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['TTL', key], fields); + }, + transformReply: undefined as unknown as () => NullReply | ArrayReply<NumberReply> +} as const satisfies Command; diff --git a/packages/client/lib/commands/PING.spec.ts b/packages/client/lib/commands/PING.spec.ts index 0cd75a6a8de..7d49b42c23b 100644 --- a/packages/client/lib/commands/PING.spec.ts +++ b/packages/client/lib/commands/PING.spec.ts @@ -19,13 +19,12 @@ describe('PING', () => { }); }); - testUtils.testAll('ping', async client => { + testUtils.testWithClient('ping', async client => { assert.equal( await client.ping(), 'PONG' ); }, { - client: GLOBAL.SERVERS.OPEN, - cluster: GLOBAL.CLUSTERS.OPEN + ...GLOBAL.SERVERS.OPEN, }); }); diff --git a/packages/client/lib/commands/index.ts b/packages/client/lib/commands/index.ts index b2898988386..71455e34e53 100644 --- a/packages/client/lib/commands/index.ts +++ b/packages/client/lib/commands/index.ts @@ -133,6 +133,9 @@ import FUNCTION_STATS from './FUNCTION_STATS'; import HDEL from './HDEL'; import HELLO from './HELLO'; import HEXISTS from './HEXISTS'; +import HEXPIRE from './HEXPIRE'; +import HEXPIREAT from './HEXPIREAT'; +import HEXPIRETIME from './HEXPIRETIME'; import HGET from './HGET'; import HGETALL from './HGETALL'; import HINCRBY from './HINCRBY'; @@ -140,6 +143,12 @@ import HINCRBYFLOAT from './HINCRBYFLOAT'; import HKEYS from './HKEYS'; import HLEN from './HLEN'; import HMGET from './HMGET'; +import HPERSIST from './HPERSIST'; +import HPEXPIRE from './HPEXPIRE'; +import HPEXPIREAT from './HPEXPIREAT'; +import HPEXPIRETIME from './HPEXPIRETIME'; +import HPTTL from './HPTTL'; +import HTTL from './HTTL'; import HRANDFIELD_COUNT_WITHVALUES from './HRANDFIELD_COUNT_WITHVALUES'; import HRANDFIELD_COUNT from './HRANDFIELD_COUNT'; import HRANDFIELD from './HRANDFIELD'; @@ -601,6 +610,12 @@ export default { hello: HELLO, HEXISTS, hExists: HEXISTS, + HEXPIRE, + hExpire: HEXPIRE, + HEXPIREAT, + hExpireAt: HEXPIREAT, + HEXPIRETIME, + hExpireTime: HEXPIRETIME, HGET, hGet: HGET, HGETALL, @@ -615,6 +630,18 @@ export default { hLen: HLEN, HMGET, hmGet: HMGET, + HPERSIST, + hPersist: HPERSIST, + HPEXPIRE, + hpExpire: HPEXPIRE, + HPEXPIREAT, + hpExpireAt: HPEXPIRE, + HPEXPIRETIME, + hpExpireTime: HPEXPIRETIME, + HPTTL, + hpTTL: HPTTL, + HTTL, + hTTL: HTTL, HRANDFIELD_COUNT_WITHVALUES, hRandFieldCountWithValues: HRANDFIELD_COUNT_WITHVALUES, HRANDFIELD_COUNT,