Skip to content

Commit

Permalink
Node: add binary support for mset, msetnx (#2229)
Browse files Browse the repository at this point in the history
* Node: add mset, mgetnx with binary

---------

Signed-off-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
acarbonetto authored Sep 6, 2024
1 parent 79fdec5 commit c356b6c
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#### Changes
* Node: Added binary variant to HSCAN command ([#2240](https://github.com/valkey-io/valkey-glide/pull/2240))
* Node: Added binary variant to sorted set commands ([#2190](https://github.com/valkey-io/valkey-glide/pull/2190), [#2210](https://github.com/valkey-io/valkey-glide/pull/2210))
* Node: Added binary variant for MSET, MSETNX commands ([#2229](https://github.com/valkey-io/valkey-glide/pull/2229))
* Node: Added binary variant to HASH commands ([#2194](https://github.com/valkey-io/valkey-glide/pull/2194))
* Node: Added binary variant to server management commands ([#2179](https://github.com/valkey-io/valkey-glide/pull/2179))
* Node: Added/updated binary variant to connection management commands and WATCH/UNWATCH ([#2160](https://github.com/valkey-io/valkey-glide/pull/2160))
Expand Down
79 changes: 54 additions & 25 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,26 +319,51 @@ export type SortedSetDataType = {
score: number;
}[];

/**
* This function converts an input from GlideRecord or Record types to GlideRecord.
*
* @param keysAndValues - key names and their values.
* @returns GlideRecord array containing keys and their values.
*/
export function convertGlideRecord(
keysAndValues: GlideRecord<GlideString> | Record<string, GlideString>,
): GlideRecord<GlideString> {
if (!Array.isArray(keysAndValues)) {
return Object.entries(keysAndValues).map((e) => {
return { key: e[0], value: e[1] };
});
}

return keysAndValues;
}

/**
* Data type which represents how data are returned from hashes or insterted there.
* Similar to `Record<GlideString, GlideString>` - see {@link GlideRecord}.
*/
export type HashDataType = {
/** The hash element name. */
field: GlideString;
/** The hash element value. */
value: GlideString;
}[];

/**
* This function converts an input from HashDataType or Record types to HashDataType.
*
* @param fieldsAndValues - field names and their values.
* @returns HashDataType array containing field names and their values.
*/
export function convertFieldsAndValuesForHset(
export function convertHashDataType(
fieldsAndValues: HashDataType | Record<string, GlideString>,
): HashDataType {
let finalFieldAndValues = [];

if (!Array.isArray(fieldsAndValues)) {
finalFieldAndValues = Object.entries(fieldsAndValues).map((e) => {
return Object.entries(fieldsAndValues).map((e) => {
return { field: e[0], value: e[1] };
});
} else {
finalFieldAndValues = fieldsAndValues;
}

return finalFieldAndValues;
return fieldsAndValues;
}

/**
Expand Down Expand Up @@ -381,17 +406,6 @@ class PointerResponse {
}
}

/**
* Data type which represents how data are returned from hashes or insterted there.
* Similar to `Record<GlideString, GlideString>` - see {@link GlideRecord}.
*/
export type HashDataType = {
/** The hash element name. */
field: GlideString;
/** The hash element value. */
value: GlideString;
}[];

/** Represents the credentials for connecting to a server. */
export type RedisCredentials = {
/**
Expand Down Expand Up @@ -1323,7 +1337,7 @@ export class BaseClient {
* @see {@link https://valkey.io/commands/mset/|valkey.io} for details.
* @remarks When in cluster mode, the command may route to multiple nodes when keys in `keyValueMap` map to different hash slots.
*
* @param keyValueMap - A key-value map consisting of keys and their respective values to set.
* @param keysAndValues - A list of key-value pairs to set.
* @returns always "OK".
*
* @example
Expand All @@ -1332,9 +1346,20 @@ export class BaseClient {
* const result = await client.mset({"key1": "value1", "key2": "value2"});
* console.log(result); // Output: 'OK'
* ```
*
* @example
* ```typescript
* // Example usage of mset method to set values for multiple keys (GlideRecords allow binary data in the key)
* const result = await client.mset([{key: "key1", value: "value1"}, {key: "key2", value: "value2"}]);
* console.log(result); // Output: 'OK'
* ```
*/
public async mset(keyValueMap: Record<string, string>): Promise<"OK"> {
return this.createWritePromise(createMSet(keyValueMap));
public async mset(
keysAndValues: Record<string, GlideString> | GlideRecord<GlideString>,
): Promise<"OK"> {
return this.createWritePromise(
createMSet(convertGlideRecord(keysAndValues)),
);
}

/**
Expand All @@ -1344,7 +1369,7 @@ export class BaseClient {
* @see {@link https://valkey.io/commands/msetnx/|valkey.io} for more details.
* @remarks When in cluster mode, all keys in `keyValueMap` must map to the same hash slot.
*
* @param keyValueMap - A key-value map consisting of keys and their respective values to set.
* @param keysAndValues - A list of key-value pairs to set.
* @returns `true` if all keys were set. `false` if no key was set.
*
* @example
Expand All @@ -1356,8 +1381,12 @@ export class BaseClient {
* console.log(result2); // Output: `false`
* ```
*/
public async msetnx(keyValueMap: Record<string, string>): Promise<boolean> {
return this.createWritePromise(createMSetNX(keyValueMap));
public async msetnx(
keysAndValues: Record<string, GlideString> | GlideRecord<GlideString>,
): Promise<boolean> {
return this.createWritePromise(
createMSetNX(convertGlideRecord(keysAndValues)),
);
}

/** Increments the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation.
Expand Down Expand Up @@ -1739,7 +1768,7 @@ export class BaseClient {
fieldsAndValues: HashDataType | Record<string, GlideString>,
): Promise<number> {
return this.createWritePromise(
createHSet(key, convertFieldsAndValuesForHset(fieldsAndValues)),
createHSet(key, convertHashDataType(fieldsAndValues)),
);
}

Expand Down
11 changes: 7 additions & 4 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,20 +332,23 @@ export function createMGet(keys: GlideString[]): command_request.Command {
* @internal
*/
export function createMSet(
keyValueMap: Record<string, string>,
keysAndValues: GlideRecord<GlideString>,
): command_request.Command {
return createCommand(RequestType.MSet, Object.entries(keyValueMap).flat());
return createCommand(
RequestType.MSet,
keysAndValues.flatMap((e) => [e.key, e.value]),
);
}

/**
* @internal
*/
export function createMSetNX(
keyValueMap: Record<string, string>,
keysAndValues: GlideRecord<GlideString>,
): command_request.Command {
return createCommand(
RequestType.MSetNX,
Object.entries(keyValueMap).flat(),
keysAndValues.flatMap((e) => [e.key, e.value]),
);
}

Expand Down
25 changes: 16 additions & 9 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

import {
BaseClient, // eslint-disable-line @typescript-eslint/no-unused-vars
GlideRecord,
GlideRecord, // eslint-disable-line @typescript-eslint/no-unused-vars
GlideString,
HashDataType,
convertGlideRecord,
convertHashDataType,
ReadFrom, // eslint-disable-line @typescript-eslint/no-unused-vars
SortedSetDataType, // eslint-disable-line @typescript-eslint/no-unused-vars
convertFieldsAndValuesForHset,
} from "./BaseClient";

import {
Expand Down Expand Up @@ -524,12 +525,14 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
/** Set multiple keys to multiple values in a single atomic operation.
* @see {@link https://valkey.io/commands/mset/|valkey.io} for details.
*
* @param keyValueMap - A key-value map consisting of keys and their respective values to set.
* @param keysAndValues - A list of key-value pairs to set.
*
* Command Response - always "OK".
*/
public mset(keyValueMap: Record<string, string>): T {
return this.addAndReturn(createMSet(keyValueMap));
public mset(
keysAndValues: Record<string, GlideString> | GlideRecord<GlideString>,
): T {
return this.addAndReturn(createMSet(convertGlideRecord(keysAndValues)));
}

/**
Expand All @@ -538,11 +541,15 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
*
* @see {@link https://valkey.io/commands/msetnx/|valkey.io} for details.
*
* @param keyValueMap - A key-value map consisting of keys and their respective values to set.
* @param keysAndValues - A list of key-value pairs to set.
* Command Response - `true` if all keys were set. `false` if no key was set.
*/
public msetnx(keyValueMap: Record<string, string>): T {
return this.addAndReturn(createMSetNX(keyValueMap));
public msetnx(
keysAndValues: Record<string, GlideString> | GlideRecord<GlideString>,
): T {
return this.addAndReturn(
createMSetNX(convertGlideRecord(keysAndValues)),
);
}

/** Increments the number stored at `key` by one. If `key` does not exist, it is set to 0 before performing the operation.
Expand Down Expand Up @@ -821,7 +828,7 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
fieldsAndValues: HashDataType | Record<string, GlideString>,
): T {
return this.addAndReturn(
createHSet(key, convertFieldsAndValuesForHset(fieldsAndValues)),
createHSet(key, convertHashDataType(fieldsAndValues)),
);
}

Expand Down
38 changes: 24 additions & 14 deletions node/tests/SharedTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// represents a running server instance. See first 2 test cases as examples.

import { expect, it } from "@jest/globals";
import { HashDataType } from "src/BaseClient";
import { GlideRecord, HashDataType } from "src/BaseClient";
import { v4 as uuidv4 } from "uuid";
import {
BaseClientConfiguration,
Expand Down Expand Up @@ -371,23 +371,31 @@ export function runBaseTests(config: {
const key2 = uuidv4();
const key3 = uuidv4();
const value = uuidv4();
const keyValueList = {
[key1]: value,
[key2]: value,
[key3]: value,
};
const valueEncoded = Buffer.from(value);
const keyValueList = [
{ key: key1, value },
{ key: key2, value },
{ key: key3, value },
];

expect(await client.mset(keyValueList)).toEqual("OK");
expect(
await client.mget([key1, key2, "nonExistingKey", key3]),
).toEqual([value, value, null, value]);

//mget with binary buffers
expect(await client.mset(keyValueList)).toEqual("OK");
//mget & mset with binary buffers
const key1Encoded = Buffer.from(key1);
const key3Encoded = Buffer.from(key3);
const valueEncoded = Buffer.from(value);
const keyValueListEncoded: GlideRecord<GlideString> = [
{ key: key1Encoded, value: valueEncoded },
{ key: key2, value },
{ key: key3Encoded, value: valueEncoded },
];

expect(await client.mset(keyValueListEncoded)).toEqual("OK");
expect(
await client.mget(
[key1, key2, "nonExistingKey", key3],
[key1Encoded, key2, "nonExistingKey", key3Encoded],
Decoder.Bytes,
),
).toEqual([valueEncoded, valueEncoded, null, valueEncoded]);
Expand All @@ -409,10 +417,12 @@ export function runBaseTests(config: {
[key1]: value,
[key2]: value,
};
const keyValueMap2 = {
[key2]: value,
[key3]: value,
};
const key2Encoded = Buffer.from(key2);
const valueEncoded = Buffer.from(value);
const keyValueMap2 = [
{ key: key2Encoded, value: valueEncoded },
{ key: key3, value: valueEncoded },
];

expect(await client.msetnx(keyValueMap1)).toEqual(true);

Expand Down

0 comments on commit c356b6c

Please sign in to comment.