diff --git a/CHANGELOG.md b/CHANGELOG.md index 12854040b7..8f195a8ca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ * Node: Added PFMERGE command ([#2053](https://github.com/valkey-io/valkey-glide/pull/2053)) * Node: Added WATCH and UNWATCH commands ([#2076](https://github.com/valkey-io/valkey-glide/pull/2076)) * Node: Added WAIT command ([#2113](https://github.com/valkey-io/valkey-glide/pull/2113)) +* Node: Added DUMP and RESTORE commands ([#2126](https://github.com/valkey-io/valkey-glide/pull/2126)) * Node: Added ZLEXCOUNT command ([#2022](https://github.com/valkey-io/valkey-glide/pull/2022)) * Node: Added ZREMRANGEBYLEX command ([#2025](https://github.com/valkey-io/valkey-glide/pull/2025)) * Node: Added ZRANGESTORE command ([#2068](https://github.com/valkey-io/valkey-glide/pull/2068)) diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index a2d4c92a7d..9eb5bc9a45 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -1153,8 +1153,8 @@ CompletableFuture pexpireAt( * user. * * @see valkey.io for details. - * @param key The key of the set. - * @return The serialized value of a set.
+ * @param key The key to serialize. + * @return The serialized value of the data stored at key.
* If key does not exist, null will be returned. * @example *
{@code
@@ -1171,10 +1171,10 @@ CompletableFuture pexpireAt(
      * deserializing the provided serialized value (obtained via {@link #dump}).
      *
      * @see valkey.io for details.
-     * @param key The key of the set.
+     * @param key The key to create.
      * @param ttl The expiry time (in milliseconds). If 0, the key will
      *     persist.
-     * @param value The serialized value.
+     * @param value The serialized value to deserialize and assign to key.
      * @return Return OK if successfully create a key with a value
      *      .
      * @example
@@ -1189,11 +1189,12 @@ CompletableFuture pexpireAt(
      * Create a key associated with a value that is obtained by
      * deserializing the provided serialized value (obtained via {@link #dump}).
      *
+     * @apiNote IDLETIME and FREQ modifiers cannot be set at the same time.
      * @see valkey.io for details.
-     * @param key The key of the set.
+     * @param key The key to create.
      * @param ttl The expiry time (in milliseconds). If 0, the key will
      *     persist.
-     * @param value The serialized value.
+     * @param value The serialized value to deserialize and assign to key.
      * @param restoreOptions The restore options. See {@link RestoreOptions}.
      * @return Return OK if successfully create a key with a value
      *      .
diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java
index 4620f375b8..1130e30f1c 100644
--- a/java/client/src/main/java/glide/api/models/BaseTransaction.java
+++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java
@@ -5230,9 +5230,9 @@ public  T copy(@NonNull ArgType source, @NonNull ArgType destination) {
      * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type
      *     will throw {@link IllegalArgumentException}.
      * @see valkey.io for details.
-     * @param key The key of the set.
-     * @return Command Response - The serialized value of a set. If key does not exist,
-     *     null will be returned.
+     * @param key The key to serialize.
+     * @return Command Response - The serialized value of the data stored at key.
+ * If key does not exist, null will be returned. */ public T dump(@NonNull ArgType key) { checkTypeOrThrow(key); @@ -5247,12 +5247,12 @@ public T dump(@NonNull ArgType key) { * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. * @see valkey.io for details. - * @param key The key of the set. + * @param key The key to create. * @param ttl The expiry time (in milliseconds). If 0, the key will * persist. - * @param value The serialized value. - * @return Command Response - Return OK if successfully create a key - * with a value. + * @param value The serialized value to deserialize and assign to key. + * @return Command Response - Return OK if the key was successfully + * restored with a value. */ public T restore(@NonNull ArgType key, long ttl, @NonNull byte[] value) { checkTypeOrThrow(key); @@ -5267,14 +5267,15 @@ public T restore(@NonNull ArgType key, long ttl, @NonNull byte[] value * * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. + * @apiNote IDLETIME and FREQ modifiers cannot be set at the same time. * @see valkey.io for details. - * @param key The key of the set. + * @param key The key to create. * @param ttl The expiry time (in milliseconds). If 0, the key will * persist. - * @param value The serialized value. + * @param value The serialized value to deserialize and assign to key. * @param restoreOptions The restore options. See {@link RestoreOptions}. - * @return Command Response - Return OK if successfully create a key - * with a value. + * @return Command Response - Return OK if the key was successfully + * restored with a value. */ public T restore( @NonNull ArgType key, diff --git a/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java b/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java index 4a069e5a92..a35bd40807 100644 --- a/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/RestoreOptions.java @@ -11,9 +11,10 @@ /** * Optional arguments to {@link GenericBaseCommands#restore(GlideString, long, byte[], - * RestoreOptions)} + * RestoreOptions)}. * * @see valkey.io + * @apiNote IDLETIME and FREQ modifiers cannot be set at the same time. */ @Getter @Builder diff --git a/node/npm/glide/index.ts b/node/npm/glide/index.ts index 4cf1855926..ecf8bec0ba 100644 --- a/node/npm/glide/index.ts +++ b/node/npm/glide/index.ts @@ -113,6 +113,7 @@ function initialize() { TimeUnit, RouteByAddress, Routes, + RestoreOptions, SingleNodeRoute, PeriodicChecksManualInterval, PeriodicChecks, @@ -210,6 +211,7 @@ function initialize() { ReturnTypeXinfoStream, RouteByAddress, Routes, + RestoreOptions, SingleNodeRoute, PeriodicChecksManualInterval, PeriodicChecks, diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 705dcbf6b8..c85f71630d 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -41,6 +41,7 @@ import { RangeByIndex, RangeByLex, RangeByScore, + RestoreOptions, ReturnTypeXinfoStream, ScoreFilter, SearchOrigin, @@ -68,6 +69,7 @@ import { createDecr, createDecrBy, createDel, + createDump, createExists, createExpire, createExpireAt, @@ -139,6 +141,7 @@ import { createRPushX, createRename, createRenameNX, + createRestore, createSAdd, createSCard, createSDiff, @@ -1092,6 +1095,81 @@ export class BaseClient { return this.createWritePromise(createDel(keys)); } + /** + * Serialize the value stored at `key` in a Valkey-specific format and return it to the user. + * + * @See {@link https://valkey.io/commands/dump/|valkey.io} for details. + * + * @param key - The `key` to serialize. + * @returns The serialized value of the data stored at `key`. If `key` does not exist, `null` will be returned. + * + * @example + * ```typescript + * let result = await client.dump("myKey"); + * console.log(result); // Output: the serialized value of "myKey" + * ``` + * + * @example + * ```typescript + * result = await client.dump("nonExistingKey"); + * console.log(result); // Output: `null` + * ``` + */ + public async dump(key: GlideString): Promise { + return this.createWritePromise(createDump(key), { + decoder: Decoder.Bytes, + }); + } + + /** + * Create a `key` associated with a `value` that is obtained by deserializing the provided + * serialized `value` (obtained via {@link dump}). + * + * @See {@link https://valkey.io/commands/restore/|valkey.io} for details. + * @remarks `options.idletime` and `options.frequency` modifiers cannot be set at the same time. + * + * @param key - The `key` to create. + * @param ttl - The expiry time (in milliseconds). If `0`, the `key` will persist. + * @param value - The serialized value to deserialize and assign to `key`. + * @param options - (Optional) Restore options {@link RestoreOptions}. + * @returns Return "OK" if the `key` was successfully restored with a `value`. + * + * @example + * ```typescript + * const result = await client.restore("myKey", 0, value); + * console.log(result); // Output: "OK" + * ``` + * + * @example + * ```typescript + * const result = await client.restore("myKey", 1000, value, {replace: true, absttl: true}); + * console.log(result); // Output: "OK" + * ``` + * + * @example + * ```typescript + * const result = await client.restore("myKey", 0, value, {replace: true, idletime: 10}); + * console.log(result); // Output: "OK" + * ``` + * + * @example + * ```typescript + * const result = await client.restore("myKey", 0, value, {replace: true, frequency: 10}); + * console.log(result); // Output: "OK" + * ``` + */ + public async restore( + key: GlideString, + ttl: number, + value: Buffer, + options?: RestoreOptions, + ): Promise<"OK"> { + return this.createWritePromise( + createRestore(key, ttl, value, options), + { decoder: Decoder.String }, + ); + } + /** Retrieve the values of multiple keys. * * @see {@link https://valkey.io/commands/mget/|valkey.io} for details. diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 8a6fd01ae0..3ce3bbf061 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -2803,6 +2803,77 @@ export function createMove( return createCommand(RequestType.Move, [key, dbIndex.toString()]); } +/** + * @internal + */ +export function createDump(key: GlideString): command_request.Command { + return createCommand(RequestType.Dump, [key]); +} + +/** + * Optional arguments for `RESTORE` command. + * + * @See {@link https://valkey.io/commands/restore/|valkey.io} for details. + * @remarks `IDLETIME` and `FREQ` modifiers cannot be set at the same time. + */ +export type RestoreOptions = { + /** + * Set to `true` to replace the key if it exists. + */ + replace?: boolean; + /** + * Set to `true` to specify that `ttl` argument of {@link BaseClient.restore} represents + * an absolute Unix timestamp (in milliseconds). + */ + absttl?: boolean; + /** + * Set the `IDLETIME` option with object idletime to the given key. + */ + idletime?: number; + /** + * Set the `FREQ` option with object frequency to the given key. + */ + frequency?: number; +}; + +/** + * @internal + */ +export function createRestore( + key: GlideString, + ttl: number, + value: GlideString, + options?: RestoreOptions, +): command_request.Command { + const args: GlideString[] = [key, ttl.toString(), value]; + + if (options) { + if (options.idletime !== undefined && options.frequency !== undefined) { + throw new Error( + `syntax error: both IDLETIME and FREQ cannot be set at the same time.`, + ); + } + + if (options.replace) { + args.push("REPLACE"); + } + + if (options.absttl) { + args.push("ABSTTL"); + } + + if (options.idletime !== undefined) { + args.push("IDLETIME", options.idletime.toString()); + } + + if (options.frequency !== undefined) { + args.push("FREQ", options.frequency.toString()); + } + } + + return createCommand(RequestType.Restore, args); +} + /** * Optional arguments to LPOS command. * diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 1a0e4d2866..73a1a1b007 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -5743,6 +5743,116 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "dump and restore test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = "{key}-1" + uuidv4(); + const key2 = "{key}-2" + uuidv4(); + const key3 = "{key}-3" + uuidv4(); + const nonExistingkey = "{nonExistingkey}-" + uuidv4(); + const value = "orange"; + const valueEncode = Buffer.from(value); + + expect(await client.set(key1, value)).toEqual("OK"); + + // Dump non-existing key + expect(await client.dump(nonExistingkey)).toBeNull(); + + // Dump existing key + const data = (await client.dump(key1)) as Buffer; + expect(data).not.toBeNull(); + + // Restore to a new key without option + expect(await client.restore(key2, 0, data)).toEqual("OK"); + expect(await client.get(key2, Decoder.String)).toEqual(value); + expect(await client.get(key2, Decoder.Bytes)).toEqual( + valueEncode, + ); + + // Restore to an existing key + await expect(client.restore(key2, 0, data)).rejects.toThrow( + "BUSYKEY: Target key name already exists.", + ); + + // Restore with `REPLACE` and existing key holding different value + expect(await client.sadd(key3, ["a"])).toEqual(1); + expect( + await client.restore(key3, 0, data, { replace: true }), + ).toEqual("OK"); + + // Restore with `REPLACE` option + expect( + await client.restore(key2, 0, data, { replace: true }), + ).toEqual("OK"); + + // Restore with `REPLACE`, `ABSTTL`, and positive TTL + expect( + await client.restore(key2, 1000, data, { + replace: true, + absttl: true, + }), + ).toEqual("OK"); + + // Restore with `REPLACE`, `ABSTTL`, and negative TTL + await expect( + client.restore(key2, -10, data, { + replace: true, + absttl: true, + }), + ).rejects.toThrow("Invalid TTL value"); + + // Restore with REPLACE and positive idletime + expect( + await client.restore(key2, 0, data, { + replace: true, + idletime: 10, + }), + ).toEqual("OK"); + + // Restore with REPLACE and negative idletime + await expect( + client.restore(key2, 0, data, { + replace: true, + idletime: -10, + }), + ).rejects.toThrow("Invalid IDLETIME value"); + + // Restore with REPLACE and positive frequency + expect( + await client.restore(key2, 0, data, { + replace: true, + frequency: 10, + }), + ).toEqual("OK"); + + // Restore with REPLACE and negative frequency + await expect( + client.restore(key2, 0, data, { + replace: true, + frequency: -10, + }), + ).rejects.toThrow("Invalid FREQ value"); + + // Restore only uses IDLETIME or FREQ modifiers + // Error will be raised if both options are set + await expect( + client.restore(key2, 0, data, { + replace: true, + idletime: 10, + frequency: 10, + }), + ).rejects.toThrow("syntax error"); + + // Restore with checksumto error + await expect( + client.restore(key2, 0, valueEncode, { replace: true }), + ).rejects.toThrow("DUMP payload version or checksum are wrong"); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( "pfadd test_%p", async (protocol) => { diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index c61f069a5f..d31a1a428c 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -6012,6 +6012,8 @@ async def restore( See https://valkey.io/commands/restore for more details. + Note: `IDLETIME` and `FREQ` modifiers cannot be set at the same time. + Args: key (TEncodable): The `key` to create. ttl (int): The expiry time (in milliseconds). If `0`, the `key` will persist. diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 1573d51c03..dda2d58814 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -2108,6 +2108,8 @@ def restore( See https://valkey.io/commands/restore for more details. + Note: `IDLETIME` and `FREQ` modifiers cannot be set at the same time. + Args: key (TEncodable): The `key` to create. ttl (int): The expiry time (in milliseconds). If `0`, the `key` will persist.