Skip to content

Commit

Permalink
Node: Add FUNCTION LOAD command (valkey-io#1969)
Browse files Browse the repository at this point in the history
* Add `FUNCTION LOAD` command.

Signed-off-by: Yury-Fridlyand <[email protected]>
  • Loading branch information
Yury-Fridlyand authored Jul 19, 2024
1 parent bc0e1b1 commit 34e253c
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Node: Added SDIFF command ([#1924](https://github.com/valkey-io/valkey-glide/pull/1924))
* Node: Added LOLWUT command ([#1934](https://github.com/valkey-io/valkey-glide/pull/1934))
* Node: Added LPOS command ([#1927](https://github.com/valkey-io/valkey-glide/pull/1927))
* Node: Added FUNCTION LOAD command ([#1969](https://github.com/valkey-io/valkey-glide/pull/1969))

## 1.0.0 (2024-07-09)

Expand Down
11 changes: 11 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,17 @@ export function createBLPop(
return createCommand(RequestType.BLPop, args);
}

/**
* @internal
*/
export function createFunctionLoad(
libraryCode: string,
replace?: boolean,
): command_request.Command {
const args = replace ? ["REPLACE", libraryCode] : [libraryCode];
return createCommand(RequestType.FunctionLoad, args);
}

export type StreamReadOptions = {
/**
* If set, the read request will block for the set amount of milliseconds or
Expand Down
31 changes: 30 additions & 1 deletion node/src/GlideClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
createCustomCommand,
createDBSize,
createEcho,
createFunctionLoad,
createFlushAll,
createInfo,
createLolwut,
Expand Down Expand Up @@ -358,7 +359,7 @@ export class GlideClient extends BaseClient {
*
* @example
* ```typescript
* // Example usage of time method without any argument
* // Example usage of time command
* const result = await client.time();
* console.log(result); // Output: ['1710925775', '913580']
* ```
Expand All @@ -385,6 +386,34 @@ export class GlideClient extends BaseClient {
return this.createWritePromise(createLolwut(options));
}

/**
* Loads a library to Valkey.
*
* See https://valkey.io/commands/function-load/ for details.
*
* since Valkey version 7.0.0.
*
* @param libraryCode - The source code that implements the library.
* @param replace - Whether the given library should overwrite a library with the same name if it
* already exists.
* @returns The library name that was loaded.
*
* @example
* ```typescript
* const code = "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)";
* const result = await client.functionLoad(code, true);
* console.log(result); // Output: 'mylib'
* ```
*/
public functionLoad(
libraryCode: string,
replace?: boolean,
): Promise<string> {
return this.createWritePromise(
createFunctionLoad(libraryCode, replace),
);
}

/**
* Deletes all the keys of all the existing databases. This command never fails.
* The command will be routed to all primary nodes.
Expand Down
33 changes: 33 additions & 0 deletions node/src/GlideClusterClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
createCustomCommand,
createDBSize,
createEcho,
createFunctionLoad,
createFlushAll,
createInfo,
createLolwut,
Expand Down Expand Up @@ -655,6 +656,38 @@ export class GlideClusterClient extends BaseClient {
);
}

/**
* Loads a library to Valkey.
*
* See https://valkey.io/commands/function-load/ for details.
*
* since Valkey version 7.0.0.
*
* @param libraryCode - The source code that implements the library.
* @param replace - Whether the given library should overwrite a library with the same name if it
* already exists.
* @param route - The command will be routed to a random node, unless `route` is provided, in which
* case the client will route the command to the nodes defined by `route`.
* @returns The library name that was loaded.
*
* @example
* ```typescript
* const code = "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)";
* const result = await client.functionLoad(code, true, 'allNodes');
* console.log(result); // Output: 'mylib'
* ```
*/
public functionLoad(
libraryCode: string,
replace?: boolean,
route?: Routes,
): Promise<string> {
return this.createWritePromise(
createFunctionLoad(libraryCode, replace),
toProtobufRoute(route),
);
}

/**
* Deletes all the keys of all the existing databases. This command never fails.
*
Expand Down
18 changes: 18 additions & 0 deletions node/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ import {
createZRemRangeByRank,
createZRemRangeByScore,
createZScore,
createFunctionLoad,
} from "./Commands";
import { command_request } from "./ProtobufMessage";

Expand Down Expand Up @@ -1737,6 +1738,23 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
return this.addAndReturn(createLolwut(options));
}

/**
* Loads a library to Valkey.
*
* See https://valkey.io/commands/function-load/ for details.
*
* since Valkey version 7.0.0.
*
* @param libraryCode - The source code that implements the library.
* @param replace - Whether the given library should overwrite a library with the same name if it
* already exists.
*
* Command Response - The library name that was loaded.
*/
public functionLoad(libraryCode: string, replace?: boolean): T {
return this.addAndReturn(createFunctionLoad(libraryCode, replace));
}

/**
* Deletes all the keys of all the existing databases. This command never fails.
*
Expand Down
105 changes: 104 additions & 1 deletion node/tests/RedisClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import {
} from "..";
import { RedisCluster } from "../../utils/TestUtils.js";
import { command_request } from "../src/ProtobufMessage";
import { runBaseTests } from "./SharedTests";
import { checkIfServerVersionLessThan, runBaseTests } from "./SharedTests";
import {
checkSimple,
convertStringArrayToBuffer,
flushAndCloseClient,
generateLuaLibCode,
getClientConfigurationOption,
intoString,
parseCommandLineArgs,
Expand Down Expand Up @@ -361,6 +362,108 @@ describe("GlideClient", () => {
TIMEOUT,
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
"function load test_%p",
async (protocol) => {
if (await checkIfServerVersionLessThan("7.0.0")) return;

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

try {
const libName = "mylib1C" + uuidv4().replaceAll("-", "");
const funcName = "myfunc1c" + uuidv4().replaceAll("-", "");
const code = generateLuaLibCode(
libName,
new Map([[funcName, "return args[1]"]]),
true,
);
// TODO use commands instead of customCommand once implemented
// verify function does not yet exist
expect(
await client.customCommand([
"FUNCTION",
"LIST",
"LIBRARYNAME",
libName,
]),
).toEqual([]);

checkSimple(await client.functionLoad(code)).toEqual(libName);

checkSimple(
await client.customCommand([
"FCALL",
funcName,
"0",
"one",
"two",
]),
).toEqual("one");
checkSimple(
await client.customCommand([
"FCALL_RO",
funcName,
"0",
"one",
"two",
]),
).toEqual("one");

// TODO verify with FUNCTION LIST
// re-load library without replace

await expect(client.functionLoad(code)).rejects.toThrow(
`Library '${libName}' already exists`,
);

// re-load library with replace
checkSimple(await client.functionLoad(code, true)).toEqual(
libName,
);

// overwrite lib with new code
const func2Name = "myfunc2c" + uuidv4().replaceAll("-", "");
const newCode = generateLuaLibCode(
libName,
new Map([
[funcName, "return args[1]"],
[func2Name, "return #args"],
]),
true,
);
checkSimple(await client.functionLoad(newCode, true)).toEqual(
libName,
);

expect(
await client.customCommand([
"FCALL",
func2Name,
"0",
"one",
"two",
]),
).toEqual(2);
expect(
await client.customCommand([
"FCALL_RO",
func2Name,
"0",
"one",
"two",
]),
).toEqual(2);
} finally {
expect(
await client.customCommand(["FUNCTION", "FLUSH"]),
).toEqual("OK");
client.close();
}
},
);

it.each([ProtocolVersion.RESP3])("simple pubsub test", async (protocol) => {
const pattern = "*";
const channel = "test-channel";
Expand Down
Loading

0 comments on commit 34e253c

Please sign in to comment.