Skip to content

Commit

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

Signed-off-by: Yury-Fridlyand <yury.fridlyand@improving.com>
  • Loading branch information
Yury-Fridlyand authored Jul 27, 2024
1 parent b753216 commit 2de6b79
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 91 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#### Changes
* Node: Added FUNCTION LIST command ([#2019](https://github.com/valkey-io/valkey-glide/pull/2019))
* Node: Added GEOSEARCH command ([#2007](https://github.com/valkey-io/valkey-glide/pull/2007))
* Node: Added LMOVE command ([#2002](https://github.com/valkey-io/valkey-glide/pull/2002))
* Node: Added GEOPOS command ([#1991](https://github.com/valkey-io/valkey-glide/pull/1991))
Expand Down
4 changes: 4 additions & 0 deletions node/npm/glide/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ function initialize() {
GlideClient,
GlideClusterClient,
GlideClientConfiguration,
FunctionListOptions,
FunctionListResponse,
SlotIdTypes,
SlotKeyTypes,
RouteByAddress,
Expand Down Expand Up @@ -158,6 +160,8 @@ function initialize() {
GlideClient,
GlideClusterClient,
GlideClientConfiguration,
FunctionListOptions,
FunctionListResponse,
SlotIdTypes,
SlotKeyTypes,
RouteByAddress,
Expand Down
35 changes: 35 additions & 0 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,41 @@ export function createFunctionLoad(
return createCommand(RequestType.FunctionLoad, args);
}

/** Optional arguments for `FUNCTION LIST` command. */
export type FunctionListOptions = {
/** A wildcard pattern for matching library names. */
libNamePattern?: string;
/** Specifies whether to request the library code from the server or not. */
withCode?: boolean;
};

/** Type of the response of `FUNCTION LIST` command. */
export type FunctionListResponse = Record<
string,
string | Record<string, string | string[]>[]
>[];

/**
* @internal
*/
export function createFunctionList(
options?: FunctionListOptions,
): command_request.Command {
const args: string[] = [];

if (options) {
if (options.libNamePattern) {
args.push("LIBRARYNAME", options.libNamePattern);
}

if (options.withCode) {
args.push("WITHCODE");
}
}

return createCommand(RequestType.FunctionList, args);
}

/**
* Represents offsets specifying a string interval to analyze in the {@link BaseClient.bitcount|bitcount} command. The offsets are
* zero-based indexes, with `0` being the first index of the string, `1` being the next index and so on.
Expand Down
38 changes: 38 additions & 0 deletions node/src/GlideClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from "./BaseClient";
import {
FlushMode,
FunctionListOptions,
FunctionListResponse,
InfoOptions,
LolwutOptions,
createClientGetName,
Expand All @@ -26,6 +28,7 @@ import {
createFlushDB,
createFunctionDelete,
createFunctionFlush,
createFunctionList,
createFunctionLoad,
createInfo,
createLolwut,
Expand Down Expand Up @@ -457,6 +460,41 @@ export class GlideClient extends BaseClient {
return this.createWritePromise(createFunctionFlush(mode));
}

/**
* Returns information about the functions and libraries.
*
* See https://valkey.io/commands/function-list/ for details.
*
* since Valkey version 7.0.0.
*
* @param options - Parameters to filter and request additional info.
* @returns Info about all or selected libraries and their functions in {@link FunctionListResponse} format.
*
* @example
* ```typescript
* // Request info for specific library including the source code
* const result1 = await client.functionList({ libNamePattern: "myLib*", withCode: true });
* // Request info for all libraries
* const result2 = await client.functionList();
* console.log(result2); // Output:
* // [{
* // "library_name": "myLib5_backup",
* // "engine": "LUA",
* // "functions": [{
* // "name": "myfunc",
* // "description": null,
* // "flags": [ "no-writes" ],
* // }],
* // "library_code": "#!lua name=myLib5_backup \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
* // }]
* ```
*/
public async functionList(
options?: FunctionListOptions,
): Promise<FunctionListResponse> {
return this.createWritePromise(createFunctionList(options));
}

/**
* Deletes all the keys of all the existing databases. This command never fails.
*
Expand Down
44 changes: 44 additions & 0 deletions node/src/GlideClusterClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from "./BaseClient";
import {
FlushMode,
FunctionListOptions,
FunctionListResponse,
InfoOptions,
LolwutOptions,
createClientGetName,
Expand All @@ -28,6 +30,7 @@ import {
createFlushDB,
createFunctionDelete,
createFunctionFlush,
createFunctionList,
createFunctionLoad,
createInfo,
createLolwut,
Expand Down Expand Up @@ -807,6 +810,47 @@ export class GlideClusterClient extends BaseClient {
);
}

/**
* Returns information about the functions and libraries.
*
* See https://valkey.io/commands/function-list/ for details.
*
* since Valkey version 7.0.0.
*
* @param options - Parameters to filter and request additional info.
* @param route - The client will route the command to the nodes defined by `route`.
* If not defined, the command will be routed to a random route.
* @returns Info about all or selected libraries and their functions in {@link FunctionListResponse} format.
*
* @example
* ```typescript
* // Request info for specific library including the source code
* const result1 = await client.functionList({ libNamePattern: "myLib*", withCode: true });
* // Request info for all libraries
* const result2 = await client.functionList();
* console.log(result2); // Output:
* // [{
* // "library_name": "myLib5_backup",
* // "engine": "LUA",
* // "functions": [{
* // "name": "myfunc",
* // "description": null,
* // "flags": [ "no-writes" ],
* // }],
* // "library_code": "#!lua name=myLib5_backup \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
* // }]
* ```
*/
public async functionList(
options?: FunctionListOptions,
route?: Routes,
): Promise<ClusterResponse<FunctionListResponse>> {
return this.createWritePromise(
createFunctionList(options),
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 @@ -10,6 +10,8 @@ import {
CoordOrigin, // eslint-disable-line @typescript-eslint/no-unused-vars
ExpireOptions,
FlushMode,
FunctionListOptions,
FunctionListResponse, // eslint-disable-line @typescript-eslint/no-unused-vars
GeoAddOptions,
GeoBoxShape, // eslint-disable-line @typescript-eslint/no-unused-vars
GeoCircleShape, // eslint-disable-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -62,6 +64,7 @@ import {
createFlushDB,
createFunctionDelete,
createFunctionFlush,
createFunctionList,
createFunctionLoad,
createGeoAdd,
createGeoDist,
Expand Down Expand Up @@ -2069,6 +2072,21 @@ export class BaseTransaction<T extends BaseTransaction<T>> {
return this.addAndReturn(createFunctionFlush(mode));
}

/**
* Returns information about the functions and libraries.
*
* See https://valkey.io/commands/function-list/ for details.
*
* since Valkey version 7.0.0.
*
* @param options - Parameters to filter and request additional info.
*
* Command Response - Info about all or selected libraries and their functions in {@link FunctionListResponse} format.
*/
public functionList(options?: FunctionListOptions): T {
return this.addAndReturn(createFunctionList(options));
}

/**
* Deletes all the keys of all the existing databases. This command never fails.
*
Expand Down
90 changes: 42 additions & 48 deletions node/tests/RedisClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { FlushMode } from "../build-ts/src/Commands";
import { command_request } from "../src/ProtobufMessage";
import { runBaseTests } from "./SharedTests";
import {
checkFunctionListResponse,
checkSimple,
convertStringArrayToBuffer,
flushAndCloseClient,
Expand Down Expand Up @@ -382,16 +383,7 @@ describe("GlideClient", () => {
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([]);
expect(await client.functionList()).toEqual([]);

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

Expand All @@ -402,7 +394,23 @@ describe("GlideClient", () => {
await client.fcallReadonly(funcName, [], ["one", "two"]),
).toEqual("one");

// TODO verify with FUNCTION LIST
let functionList = await client.functionList({
libNamePattern: libName,
});
let expectedDescription = new Map<string, string | null>([
[funcName, null],
]);
let expectedFlags = new Map<string, string[]>([
[funcName, ["no-writes"]],
]);

checkFunctionListResponse(
functionList,
libName,
expectedDescription,
expectedFlags,
);

// re-load library without replace

await expect(client.functionLoad(code)).rejects.toThrow(
Expand All @@ -428,6 +436,24 @@ describe("GlideClient", () => {
libName,
);

functionList = await client.functionList({ withCode: true });
expectedDescription = new Map<string, string | null>([
[funcName, null],
[func2Name, null],
]);
expectedFlags = new Map<string, string[]>([
[funcName, ["no-writes"]],
[func2Name, ["no-writes"]],
]);

checkFunctionListResponse(
functionList,
libName,
expectedDescription,
expectedFlags,
newCode,
);

checkSimple(
await client.fcall(func2Name, [], ["one", "two"]),
).toEqual(2);
Expand Down Expand Up @@ -459,16 +485,8 @@ describe("GlideClient", () => {
true,
);

// TODO use commands instead of customCommand once implemented
// verify function does not yet exist
expect(
await client.customCommand([
"FUNCTION",
"LIST",
"LIBRARYNAME",
libName,
]),
).toEqual([]);
expect(await client.functionList()).toEqual([]);

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

Expand All @@ -480,16 +498,8 @@ describe("GlideClient", () => {
"OK",
);

// TODO use commands instead of customCommand once implemented
// verify function does not yet exist
expect(
await client.customCommand([
"FUNCTION",
"LIST",
"LIBRARYNAME",
libName,
]),
).toEqual([]);
expect(await client.functionList()).toEqual([]);

// Attempt to re-load library without overwriting to ensure FLUSH was effective
checkSimple(await client.functionLoad(code)).toEqual(libName);
Expand Down Expand Up @@ -517,32 +527,16 @@ describe("GlideClient", () => {
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([]);
expect(await client.functionList()).toEqual([]);

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

// Delete the function
expect(await client.functionDelete(libName)).toEqual("OK");

// TODO use commands instead of customCommand once implemented
// verify function does not exist
expect(
await client.customCommand([
"FUNCTION",
"LIST",
"LIBRARYNAME",
libName,
]),
).toEqual([]);
// verify function does not yet exist
expect(await client.functionList()).toEqual([]);

// deleting a non-existing library
await expect(client.functionDelete(libName)).rejects.toThrow(
Expand Down
Loading

0 comments on commit 2de6b79

Please sign in to comment.