Skip to content

Commit 6b5e977

Browse files
authored
Go: Add Function List (valkey-io#3673)
Signed-off-by: TJ Zhang <tj.zhang@improving.com>
1 parent 87bb61b commit 6b5e977

11 files changed

+373
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
* Go: Add Function Kill ([#3604](https://github.com/valkey-io/valkey-glide/pull/3604))
6565
* Benchmarks: Fix rust benchmark latencies calculation
6666
* Python: Moved the tests folder to be under the root folder ([#3661](https://github.com/valkey-io/valkey-glide/pull/3661))
67+
* Go: Add Function List ([#3673](https://github.com/valkey-io/valkey-glide/pull/3673))
6768

6869
#### Breaking Changes
6970

go/api/base_client.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7885,3 +7885,28 @@ func (client *baseClient) FunctionKill() (string, error) {
78857885
}
78867886
return handleStringResponse(result)
78877887
}
7888+
7889+
// Returns information about the functions and libraries.
7890+
//
7891+
// Since:
7892+
//
7893+
// Valkey 7.0 and above.
7894+
//
7895+
// See [valkey.io] for details.
7896+
//
7897+
// Parameters:
7898+
//
7899+
// query - The query to use to filter the functions and libraries.
7900+
//
7901+
// Return value:
7902+
//
7903+
// A list of info about queried libraries and their functions.
7904+
//
7905+
// [valkey.io]: https://valkey.io/commands/function-list/
7906+
func (client *baseClient) FunctionList(query FunctionListQuery) ([]LibraryInfo, error) {
7907+
response, err := client.executeCommand(C.FunctionList, query.ToArgs())
7908+
if err != nil {
7909+
return nil, err
7910+
}
7911+
return handleFunctionListResponse(response)
7912+
}

go/api/function_command_results.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package api
44

55
import (
66
"time"
7+
8+
"github.com/valkey-io/valkey-glide/go/api/options"
79
)
810

911
type Engine struct {
@@ -32,3 +34,34 @@ type FunctionStatsResult struct {
3234
// Information about the currently running script, if any
3335
RunningScript RunningScript
3436
}
37+
38+
type FunctionListQuery struct {
39+
// The name of the library to query, use empty string for all libraries
40+
LibraryName string
41+
// Whether to include the code of the library
42+
WithCode bool
43+
}
44+
45+
func (query FunctionListQuery) ToArgs() []string {
46+
args := []string{}
47+
if query.LibraryName != "" {
48+
args = append(args, options.LibraryNameKeyword, query.LibraryName)
49+
}
50+
if query.WithCode {
51+
args = append(args, options.WithCodeKeyword)
52+
}
53+
return args
54+
}
55+
56+
type FunctionInfo struct {
57+
Name string
58+
Description string
59+
Flags []string
60+
}
61+
62+
type LibraryInfo struct {
63+
Name string
64+
Engine string
65+
Functions []FunctionInfo
66+
Code string
67+
}

go/api/glide_cluster_client.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,49 @@ func (client *GlideClusterClient) FunctionKillWithRoute(route options.RouteOptio
15821582
return handleOkResponse(result)
15831583
}
15841584

1585+
// Returns information about the functions and libraries.
1586+
//
1587+
// Since:
1588+
//
1589+
// Valkey 7.0 and above.
1590+
//
1591+
// See [valkey.io] for details.
1592+
//
1593+
// Parameters:
1594+
//
1595+
// query - The query to use to filter the functions and libraries.
1596+
// route - Specifies the routing configuration for the command. The client will route the
1597+
// command to the nodes defined by route.
1598+
//
1599+
// Return value:
1600+
//
1601+
// A [ClusterValue] containing a list of info about queried libraries and their functions.
1602+
//
1603+
// [valkey.io]: https://valkey.io/commands/function-list/
1604+
func (client *GlideClusterClient) FunctionListWithRoute(
1605+
query FunctionListQuery,
1606+
route options.RouteOption,
1607+
) (ClusterValue[[]LibraryInfo], error) {
1608+
response, err := client.executeCommandWithRoute(C.FunctionList, query.ToArgs(), route.Route)
1609+
if err != nil {
1610+
return createEmptyClusterValue[[]LibraryInfo](), err
1611+
}
1612+
1613+
if route.Route != nil && route.Route.IsMultiNode() {
1614+
multiNodeLibs, err := handleFunctionListMultiNodeResponse(response)
1615+
if err != nil {
1616+
return createEmptyClusterValue[[]LibraryInfo](), err
1617+
}
1618+
return createClusterMultiValue[[]LibraryInfo](multiNodeLibs), nil
1619+
}
1620+
1621+
libs, err := handleFunctionListResponse(response)
1622+
if err != nil {
1623+
return createEmptyClusterValue[[]LibraryInfo](), err
1624+
}
1625+
return createClusterSingleValue[[]LibraryInfo](libs), nil
1626+
}
1627+
15851628
// Publish posts a message to the specified channel. Returns the number of clients that received the message.
15861629
//
15871630
// Channel can be any string, but common patterns include using "." to create namespaces like

go/api/options/constants.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const (
3737
MinIdKeyword string = "MINID"
3838
GroupKeyword string = "GROUP"
3939
StreamsKeyword string = "STREAMS"
40+
WithCodeKeyword string = "WITHCODE"
41+
LibraryNameKeyword string = "LIBRARYNAME"
4042
)
4143

4244
type InfBoundary string

go/api/response_handlers.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,3 +1572,84 @@ func handleFunctionStatsResponse(response *C.struct_CommandResponse) (map[string
15721572

15731573
return result, nil
15741574
}
1575+
1576+
func parseFunctionInfo(items any) []FunctionInfo {
1577+
result := make([]FunctionInfo, 0, len(items.([]interface{})))
1578+
for _, item := range items.([]interface{}) {
1579+
if function, ok := item.(map[string]interface{}); ok {
1580+
// Handle nullable description
1581+
var description string
1582+
if desc, ok := function["description"].(string); ok {
1583+
description = desc
1584+
}
1585+
1586+
// Handle flags map
1587+
flags := make([]string, 0)
1588+
if flagsMap, ok := function["flags"].(map[string]struct{}); ok {
1589+
for flag := range flagsMap {
1590+
flags = append(flags, flag)
1591+
}
1592+
}
1593+
1594+
result = append(result, FunctionInfo{
1595+
Name: function["name"].(string),
1596+
Description: description,
1597+
Flags: flags,
1598+
})
1599+
}
1600+
}
1601+
return result
1602+
}
1603+
1604+
func parseLibraryInfo(itemMap map[string]interface{}) LibraryInfo {
1605+
libraryInfo := LibraryInfo{
1606+
Name: itemMap["library_name"].(string),
1607+
Engine: itemMap["engine"].(string),
1608+
Functions: parseFunctionInfo(itemMap["functions"]),
1609+
}
1610+
// Handle optional library_code field
1611+
if code, ok := itemMap["library_code"].(string); ok {
1612+
libraryInfo.Code = code
1613+
}
1614+
return libraryInfo
1615+
}
1616+
1617+
func handleFunctionListResponse(response *C.struct_CommandResponse) ([]LibraryInfo, error) {
1618+
if err := checkResponseType(response, C.Array, false); err != nil {
1619+
return nil, err
1620+
}
1621+
1622+
data, err := parseArray(response)
1623+
if err != nil {
1624+
return nil, err
1625+
}
1626+
result := make([]LibraryInfo, 0, len(data.([]interface{})))
1627+
for _, item := range data.([]interface{}) {
1628+
if itemMap, ok := item.(map[string]interface{}); ok {
1629+
result = append(result, parseLibraryInfo(itemMap))
1630+
}
1631+
}
1632+
return result, nil
1633+
}
1634+
1635+
func handleFunctionListMultiNodeResponse(response *C.struct_CommandResponse) (map[string][]LibraryInfo, error) {
1636+
data, err := handleStringToAnyMapResponse(response)
1637+
if err != nil {
1638+
return nil, err
1639+
}
1640+
1641+
multiNodeLibs := make(map[string][]LibraryInfo)
1642+
for node, nodeData := range data {
1643+
// nodeData is already parsed into a Go array of interfaces
1644+
if nodeArray, ok := nodeData.([]interface{}); ok {
1645+
libs := make([]LibraryInfo, 0, len(nodeArray))
1646+
for _, item := range nodeArray {
1647+
if itemMap, ok := item.(map[string]interface{}); ok {
1648+
libs = append(libs, parseLibraryInfo(itemMap))
1649+
}
1650+
}
1651+
multiNodeLibs[node] = libs
1652+
}
1653+
}
1654+
return multiNodeLibs, nil
1655+
}

go/api/scripting_and_function_base_commands.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@ type ScriptingAndFunctionBaseCommands interface {
2626
FCallReadOnlyWithKeysAndArgs(function string, keys []string, args []string) (any, error)
2727

2828
FunctionKill() (string, error)
29+
30+
FunctionList(query FunctionListQuery) ([]LibraryInfo, error)
2931
}

go/api/scripting_and_function_cluster_commands.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,6 @@ type ScriptingAndFunctionClusterCommands interface {
4242
FunctionDeleteWithRoute(libName string, route options.RouteOption) (string, error)
4343

4444
FunctionKillWithRoute(route options.RouteOption) (string, error)
45+
46+
FunctionListWithRoute(query FunctionListQuery, route options.RouteOption) (ClusterValue[[]LibraryInfo], error)
4547
}

go/api/scripting_and_function_commands_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,3 +746,111 @@ func ExampleGlideClusterClient_FunctionKillWithRoute() {
746746
// Output:
747747
// Expected error: An error was signalled by the server: - NotBusy: No scripts in execution right now.
748748
}
749+
750+
func ExampleGlideClient_FunctionList() {
751+
client := getExampleGlideClient()
752+
753+
// Load a function first
754+
_, err := client.FunctionLoad(libraryCode, true)
755+
if err != nil {
756+
fmt.Println("Glide example failed with an error: ", err)
757+
return
758+
}
759+
760+
query := FunctionListQuery{
761+
LibraryName: "mylib",
762+
WithCode: true,
763+
}
764+
765+
libs, err := client.FunctionList(query)
766+
if err != nil {
767+
fmt.Println("Glide example failed with an error: ", err)
768+
}
769+
770+
fmt.Printf("There are %d libraries loaded.\n", len(libs))
771+
for i, lib := range libs {
772+
fmt.Printf("%d) Library name '%s', on engine %s, with %d functions\n", i+1, lib.Name, lib.Engine, len(lib.Functions))
773+
for j, fn := range lib.Functions {
774+
fmt.Printf(" %d) function '%s'\n", j+1, fn.Name)
775+
}
776+
}
777+
// Output:
778+
// There are 1 libraries loaded.
779+
// 1) Library name 'mylib', on engine LUA, with 1 functions
780+
// 1) function 'myfunc'
781+
}
782+
783+
func ExampleGlideClusterClient_FunctionList() {
784+
client := getExampleGlideClusterClient()
785+
786+
// Load a function first
787+
_, err := client.FunctionLoad(libraryCode, true)
788+
if err != nil {
789+
fmt.Println("Glide example failed with an error: ", err)
790+
return
791+
}
792+
793+
query := FunctionListQuery{
794+
LibraryName: "mylib",
795+
WithCode: true,
796+
}
797+
798+
libs, err := client.FunctionList(query)
799+
if err != nil {
800+
fmt.Println("Glide example failed with an error: ", err)
801+
}
802+
803+
fmt.Printf("There are %d libraries loaded.\n", len(libs))
804+
for i, lib := range libs {
805+
fmt.Printf("%d) Library name '%s', on engine %s, with %d functions\n", i+1, lib.Name, lib.Engine, len(lib.Functions))
806+
for j, fn := range lib.Functions {
807+
fmt.Printf(" %d) function '%s'\n", j+1, fn.Name)
808+
}
809+
}
810+
// Output:
811+
// There are 1 libraries loaded.
812+
// 1) Library name 'mylib', on engine LUA, with 1 functions
813+
// 1) function 'myfunc'
814+
}
815+
816+
func ExampleGlideClusterClient_FunctionListWithRoute() {
817+
client := getExampleGlideClusterClient()
818+
819+
// Load a function first
820+
route := config.Route(config.AllPrimaries)
821+
opts := options.RouteOption{
822+
Route: route,
823+
}
824+
_, err := client.FunctionLoadWithRoute(libraryCode, true, opts)
825+
if err != nil {
826+
fmt.Println("Glide example failed with an error: ", err)
827+
return
828+
}
829+
830+
// List functions with route
831+
query := FunctionListQuery{
832+
WithCode: true,
833+
}
834+
result, err := client.FunctionListWithRoute(query, opts)
835+
if err != nil {
836+
fmt.Println("Glide example failed with an error: ", err)
837+
return
838+
}
839+
840+
// Print results for each node
841+
for _, libs := range result.MultiValue() {
842+
fmt.Println("Example Node:")
843+
for _, lib := range libs {
844+
fmt.Printf(" Library: %s\n", lib.Name)
845+
fmt.Printf(" Engine: %s\n", lib.Engine)
846+
fmt.Printf(" Functions: %d\n", len(lib.Functions))
847+
}
848+
break
849+
}
850+
851+
// Output:
852+
// Example Node:
853+
// Library: mylib
854+
// Engine: LUA
855+
// Functions: 1
856+
}

0 commit comments

Comments
 (0)