Skip to content

Commit

Permalink
Node: added COPY command (#2024)
Browse files Browse the repository at this point in the history
* Node: added COPY command

Signed-off-by: Yi-Pin Chen <[email protected]>
  • Loading branch information
yipin-chen authored Jul 29, 2024
1 parent 054cc3d commit 089be12
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* Node: Added FUNCTION DELETE command ([#1990](https:/valkey-io/valkey-glide/pull/1990))
* Node: Added FUNCTION FLUSH command ([#1984](https:/valkey-io/valkey-glide/pull/1984))
* Node: Added FCALL and FCALL_RO commands ([#2011](https:/valkey-io/valkey-glide/pull/2011))
* Node: Added COPY command ([#2024](https:/valkey-io/valkey-glide/pull/2024))
* Node: Added ZMPOP command ([#1994](https:/valkey-io/valkey-glide/pull/1994))
* Node: Added ZINCRBY command ([#2009](https:/valkey-io/valkey-glide/pull/2009))
* Node: Added BZMPOP command ([#2018](https:/valkey-io/valkey-glide/pull/2018))
Expand Down
8 changes: 4 additions & 4 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import * as net from "net";
import { Buffer, BufferWriter, Reader, Writer } from "protobufjs";
import {
AggregationType,
BitmapIndexType,
BitOffsetOptions,
BitmapIndexType,
BitwiseOperation,
CoordOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars
ExpireOptions,
Expand All @@ -21,13 +21,13 @@ import {
GeoCircleShape, // eslint-disable-line @typescript-eslint/no-unused-vars
GeoSearchResultOptions,
GeoSearchShape,
GeospatialData,
GeoUnit,
GeospatialData,
InsertPosition,
KeyWeight,
MemberOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars
LPosOptions,
ListDirection,
MemberOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars
RangeByIndex,
RangeByLex,
RangeByScore,
Expand All @@ -41,9 +41,9 @@ import {
ZAddOptions,
createBLPop,
createBRPop,
createBZMPop,
createBitCount,
createBitOp,
createBZMPop,
createBitPos,
createDecr,
createDecrBy,
Expand Down
24 changes: 24 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,30 @@ export function createFlushDB(mode?: FlushMode): command_request.Command {
}
}

/**
*
* @internal
*/
export function createCopy(
source: string,
destination: string,
options?: { destinationDB?: number; replace?: boolean },
): command_request.Command {
let args: string[] = [source, destination];

if (options) {
if (options.destinationDB !== undefined) {
args = args.concat("DB", options.destinationDB.toString());
}

if (options.replace) {
args.push("REPLACE");
}
}

return createCommand(RequestType.Copy, args);
}

/**
* Optional arguments to LPOS command.
*
Expand Down
43 changes: 43 additions & 0 deletions node/src/GlideClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
createConfigResetStat,
createConfigRewrite,
createConfigSet,
createCopy,
createCustomCommand,
createDBSize,
createEcho,
Expand Down Expand Up @@ -374,6 +375,48 @@ export class GlideClient extends BaseClient {
return this.createWritePromise(createTime());
}

/**
* Copies the value stored at the `source` to the `destination` key. If `destinationDB` is specified,
* the value will be copied to the database specified, otherwise the current database will be used.
* When `replace` is true, removes the `destination` key first if it already exists, otherwise performs
* no action.
*
* See https://valkey.io/commands/copy/ for more details.
*
* @param source - The key to the source value.
* @param destination - The key where the value should be copied to.
* @param destinationDB - (Optional) The alternative logical database index for the destination key.
* If not provided, the current database will be used.
* @param replace - (Optional) If `true`, the `destination` key should be removed before copying the
* value to it. If not provided, no action will be performed if the key already exists.
* @returns `true` if `source` was copied, `false` if the `source` was not copied.
*
* since Valkey version 6.2.0.
*
* @example
* ```typescript
* const result = await client.copy("set1", "set2");
* console.log(result); // Output: true - "set1" was copied to "set2".
* ```
* ```typescript
* const result = await client.copy("set1", "set2", { replace: true });
* console.log(result); // Output: true - "set1" was copied to "set2".
* ```
* ```typescript
* const result = await client.copy("set1", "set2", { destinationDB: 1, replace: false });
* console.log(result); // Output: true - "set1" was copied to "set2".
* ```
*/
public async copy(
source: string,
destination: string,
options?: { destinationDB?: number; replace?: boolean },
): Promise<boolean> {
return this.createWritePromise(
createCopy(source, destination, options),
);
}

/**
* Displays a piece of generative computer art and the server version.
*
Expand Down
32 changes: 32 additions & 0 deletions node/src/GlideClusterClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
createConfigResetStat,
createConfigRewrite,
createConfigSet,
createCopy,
createCustomCommand,
createDBSize,
createEcho,
Expand Down Expand Up @@ -638,6 +639,37 @@ export class GlideClusterClient extends BaseClient {
return this.createWritePromise(createTime(), toProtobufRoute(route));
}

/**
* Copies the value stored at the `source` to the `destination` key. When `replace` is `true`,
* removes the `destination` key first if it already exists, otherwise performs no action.
*
* See https://valkey.io/commands/copy/ for more details.
*
* @remarks When in cluster mode, `source` and `destination` must map to the same hash slot.
* @param source - The key to the source value.
* @param destination - The key where the value should be copied to.
* @param replace - (Optional) If `true`, the `destination` key should be removed before copying the
* value to it. If not provided, no action will be performed if the key already exists.
* @returns `true` if `source` was copied, `false` if the `source` was not copied.
*
* since Valkey version 6.2.0.
*
* @example
* ```typescript
* const result = await client.copy("set1", "set2", true);
* console.log(result); // Output: true - "set1" was copied to "set2".
* ```
*/
public async copy(
source: string,
destination: string,
replace?: boolean,
): Promise<boolean> {
return this.createWritePromise(
createCopy(source, destination, { replace: replace }),
);
}

/**
* Displays a piece of generative computer art and the server version.
*
Expand Down
57 changes: 55 additions & 2 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
GeoCircleShape, // eslint-disable-line @typescript-eslint/no-unused-vars
GeoSearchResultOptions,
GeoSearchShape,
GeospatialData,
GeoUnit,
GeospatialData,
InfoOptions,
InsertPosition,
KeyWeight,
Expand Down Expand Up @@ -49,6 +49,7 @@ import {
createConfigResetStat,
createConfigRewrite,
createConfigSet,
createCopy,
createCustomCommand,
createDBSize,
createDecr,
Expand Down Expand Up @@ -154,6 +155,7 @@ import {
createZDiff,
createZDiffStore,
createZDiffWithScores,
createZIncrBy,
createZInterCard,
createZInterstore,
createZMPop,
Expand All @@ -170,7 +172,6 @@ import {
createZRevRank,
createZRevRankWithScore,
createZScore,
createZIncrBy,
} from "./Commands";
import { command_request } from "./ProtobufMessage";

Expand Down Expand Up @@ -2436,6 +2437,33 @@ export class Transaction extends BaseTransaction<Transaction> {
public select(index: number): Transaction {
return this.addAndReturn(createSelect(index));
}

/**
* Copies the value stored at the `source` to the `destination` key. If `destinationDB` is specified,
* the value will be copied to the database specified, otherwise the current database will be used.
* When `replace` is true, removes the `destination` key first if it already exists, otherwise performs
* no action.
*
* See https://valkey.io/commands/copy/ for more details.
*
* @param source - The key to the source value.
* @param destination - The key where the value should be copied to.
* @param destinationDB - (Optional) The alternative logical database index for the destination key.
* If not provided, the current database will be used.
* @param replace - (Optional) If `true`, the `destination` key should be removed before copying the
* value to it. If not provided, no action will be performed if the key already exists.
*
* Command Response - `true` if `source` was copied, `false` if the `source` was not copied.
*
* since Valkey version 6.2.0.
*/
public copy(
source: string,
destination: string,
options?: { destinationDB?: number; replace?: boolean },
): Transaction {
return this.addAndReturn(createCopy(source, destination, options));
}
}

/**
Expand All @@ -2451,4 +2479,29 @@ export class Transaction extends BaseTransaction<Transaction> {
*/
export class ClusterTransaction extends BaseTransaction<ClusterTransaction> {
/// TODO: add all CLUSTER commands

/**
* Copies the value stored at the `source` to the `destination` key. When `replace` is true,
* removes the `destination` key first if it already exists, otherwise performs no action.
*
* See https://valkey.io/commands/copy/ for more details.
*
* @param source - The key to the source value.
* @param destination - The key where the value should be copied to.
* @param replace - (Optional) If `true`, the `destination` key should be removed before copying the
* value to it. If not provided, no action will be performed if the key already exists.
*
* Command Response - `true` if `source` was copied, `false` if the `source` was not copied.
*
* since Valkey version 6.2.0.
*/
public copy(
source: string,
destination: string,
replace?: boolean,
): ClusterTransaction {
return this.addAndReturn(
createCopy(source, destination, { replace: replace }),
);
}
}
90 changes: 90 additions & 0 deletions node/tests/RedisClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,96 @@ describe("GlideClient", () => {
TIMEOUT,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
"copy with DB test_%p",
async (protocol) => {
if (cluster.checkIfServerVersionLessThan("6.2.0")) return;

const client = await GlideClient.createClient(
getClientConfigurationOption(cluster.getAddresses(), protocol),
);

const source = `{key}-${uuidv4()}`;
const destination = `{key}-${uuidv4()}`;
const value1 = uuidv4();
const value2 = uuidv4();
const index0 = 0;
const index1 = 1;
const index2 = 2;

// neither key exists
expect(
await client.copy(source, destination, {
destinationDB: index1,
replace: false,
}),
).toEqual(false);

// source exists, destination does not
expect(await client.set(source, value1)).toEqual("OK");
expect(
await client.copy(source, destination, {
destinationDB: index1,
replace: false,
}),
).toEqual(true);
expect(await client.select(index1)).toEqual("OK");
checkSimple(await client.get(destination)).toEqual(value1);

// new value for source key
expect(await client.select(index0)).toEqual("OK");
expect(await client.set(source, value2)).toEqual("OK");

// no REPLACE, copying to existing key on DB 1, non-existing key on DB 2
expect(
await client.copy(source, destination, {
destinationDB: index1,
replace: false,
}),
).toEqual(false);
expect(
await client.copy(source, destination, {
destinationDB: index2,
replace: false,
}),
).toEqual(true);

// new value only gets copied to DB 2
expect(await client.select(index1)).toEqual("OK");
checkSimple(await client.get(destination)).toEqual(value1);
expect(await client.select(index2)).toEqual("OK");
checkSimple(await client.get(destination)).toEqual(value2);

// both exists, with REPLACE, when value isn't the same, source always get copied to
// destination
expect(await client.select(index0)).toEqual("OK");
expect(
await client.copy(source, destination, {
destinationDB: index1,
replace: true,
}),
).toEqual(true);
expect(await client.select(index1)).toEqual("OK");
checkSimple(await client.get(destination)).toEqual(value2);

//transaction tests
const transaction = new Transaction();
transaction.select(index1);
transaction.set(source, value1);
transaction.copy(source, destination, {
destinationDB: index1,
replace: true,
});
transaction.get(destination);
const results = await client.exec(transaction);

checkSimple(results).toEqual(["OK", "OK", true, value1]);

client.close();
},
TIMEOUT,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
"function load test_%p",
async (protocol) => {
Expand Down
Loading

0 comments on commit 089be12

Please sign in to comment.