From 56dead090a9494817f7f9160712ca1c76fe7f6a5 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Tue, 20 Aug 2024 17:05:06 -0700 Subject: [PATCH] Node: `FUNCTION DUMP` and `FUNCTION RESTORE` in transaction (#2173) Signed-off-by: Yury-Fridlyand Co-authored-by: Andrew Carbonetto --- CHANGELOG.md | 3 +- node/src/BaseClient.ts | 2 +- node/src/Transaction.ts | 31 +++++++++++++++++ node/tests/GlideClient.test.ts | 40 ++++++++++++++++++++++ node/tests/GlideClusterClient.test.ts | 48 +++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a401ce8748..730c391284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ #### Changes -* Node: Added FUNCTION DUMP and FUNCTION RESTORE commands ([#2129](https://github.com/valkey-io/valkey-glide/pull/2129)) +* Node: Added FUNCTION DUMP and FUNCTION RESTORE commands (transaction) ([#2173](https://github.com/valkey-io/valkey-glide/pull/2173)) +* Node: Added FUNCTION DUMP and FUNCTION RESTORE commands ([#2129](https://github.com/valkey-io/valkey-glide/pull/2129), [#2173](https://github.com/valkey-io/valkey-glide/pull/2173)) * Node: Added ZUNIONSTORE command ([#2145](https://github.com/valkey-io/valkey-glide/pull/2145)) * Node: Added XREADGROUP command ([#2124](https://github.com/valkey-io/valkey-glide/pull/2124)) * Node: Added XINFO GROUPS command ([#2122](https://github.com/valkey-io/valkey-glide/pull/2122)) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 563db515ca..f052cdc369 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -277,7 +277,7 @@ export type GlideString = string | Buffer; /** * Enum representing the different types of decoders. */ -export const enum Decoder { +export enum Decoder { /** * Decodes the response into a buffer array. */ diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 993cdf703b..212d40f670 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -27,6 +27,7 @@ import { FlushMode, FunctionListOptions, FunctionListResponse, // eslint-disable-line @typescript-eslint/no-unused-vars + FunctionRestorePolicy, FunctionStatsSingleResponse, // eslint-disable-line @typescript-eslint/no-unused-vars GeoAddOptions, GeoBoxShape, // eslint-disable-line @typescript-eslint/no-unused-vars @@ -97,9 +98,11 @@ import { createFlushAll, createFlushDB, createFunctionDelete, + createFunctionDump, createFunctionFlush, createFunctionList, createFunctionLoad, + createFunctionRestore, createFunctionStats, createGeoAdd, createGeoDist, @@ -3233,6 +3236,34 @@ export class BaseTransaction> { return this.addAndReturn(createFunctionStats()); } + /** + * Returns the serialized payload of all loaded libraries. + * + * @see {@link https://valkey.io/commands/function-dump/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * @remarks To execute a transaction with a `functionDump` command, the `exec` command requires `Decoder.Bytes` to handle the response. + * + * Command Response - The serialized payload of all loaded libraries. + */ + public functionDump(): T { + return this.addAndReturn(createFunctionDump()); + } + + /** + * Restores libraries from the serialized payload returned by {@link functionDump}. + * + * @see {@link https://valkey.io/commands/function-restore/|valkey.io} for details. + * @remarks Since Valkey version 7.0.0. + * + * @param payload - The serialized data from {@link functionDump}. + * @param policy - (Optional) A policy for handling existing libraries. + * + * Command Response - `"OK"`. + */ + public functionRestore(payload: Buffer, policy?: FunctionRestorePolicy): T { + return this.addAndReturn(createFunctionRestore(payload, policy)); + } + /** * Deletes all the keys of all the existing databases. This command never fails. * diff --git a/node/tests/GlideClient.test.ts b/node/tests/GlideClient.test.ts index 6c93134fae..5f1ccec953 100644 --- a/node/tests/GlideClient.test.ts +++ b/node/tests/GlideClient.test.ts @@ -1095,6 +1095,46 @@ describe("GlideClient", () => { }, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "function dump function restore in transaction %p", + async (protocol) => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + const client = await GlideClient.createClient(config); + expect(await client.functionFlush()).toEqual("OK"); + + try { + const name1 = "Foster"; + const name2 = "Dogster"; + // function returns first argument + const code = generateLuaLibCode( + name1, + new Map([[name2, "return args[1]"]]), + false, + ); + expect(await client.functionLoad(code)).toEqual(name1); + + // Verify functionDump + let transaction = new Transaction().functionDump(); + const result = await client.exec(transaction, Decoder.Bytes); + const data = result?.[0] as Buffer; + + // Verify functionRestore + transaction = new Transaction() + .functionRestore(data, FunctionRestorePolicy.REPLACE) + .fcall(name2, [], ["meow"]); + expect(await client.exec(transaction)).toEqual(["OK", "meow"]); + } finally { + expect(await client.functionFlush()).toEqual("OK"); + client.close(); + } + }, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( "sort sortstore sort_store sortro sort_ro sortreadonly test_%p", async (protocol) => { diff --git a/node/tests/GlideClusterClient.test.ts b/node/tests/GlideClusterClient.test.ts index 8cd18e943a..88c2af5076 100644 --- a/node/tests/GlideClusterClient.test.ts +++ b/node/tests/GlideClusterClient.test.ts @@ -25,6 +25,7 @@ import { ReturnType, Routes, ScoreFilter, + SlotKeyTypes, } from ".."; import { RedisCluster } from "../../utils/TestUtils.js"; import { @@ -1530,6 +1531,53 @@ describe("GlideClusterClient", () => { }, TIMEOUT, ); + it("function dump function restore in transaction", async () => { + if (cluster.checkIfServerVersionLessThan("7.0.0")) return; + + const config = getClientConfigurationOption( + cluster.getAddresses(), + protocol, + ); + const client = await GlideClusterClient.createClient(config); + const route: SlotKeyTypes = { + key: uuidv4(), + type: "primarySlotKey", + }; + expect(await client.functionFlush()).toEqual("OK"); + + try { + const name1 = "Foster"; + const name2 = "Dogster"; + // function returns first argument + const code = generateLuaLibCode( + name1, + new Map([[name2, "return args[1]"]]), + false, + ); + expect( + await client.functionLoad(code, true, route), + ).toEqual(name1); + + // Verify functionDump + let transaction = new ClusterTransaction().functionDump(); + const result = await client.exec(transaction, { + decoder: Decoder.Bytes, + route: route, + }); + const data = result?.[0] as Buffer; + + // Verify functionRestore + transaction = new ClusterTransaction() + .functionRestore(data, FunctionRestorePolicy.REPLACE) + .fcall(name2, [], ["meow"]); + expect( + await client.exec(transaction, { route: route }), + ).toEqual(["OK", "meow"]); + } finally { + expect(await client.functionFlush()).toEqual("OK"); + client.close(); + } + }); }, );