From 84b9e3a16883cb0413c16566cad22f5db3e76216 Mon Sep 17 00:00:00 2001 From: PragmaTwice Date: Mon, 9 Oct 2023 11:28:10 +0900 Subject: [PATCH] Add LISTLIB subcommand as an extension to FUNCTION --- src/commands/cmd_function.cc | 4 ++ src/storage/scripting.cc | 46 ++++++++++++++++++++ src/storage/scripting.h | 1 + tests/gocase/unit/scripting/function_test.go | 22 ++++++++++ 4 files changed, 73 insertions(+) diff --git a/src/commands/cmd_function.cc b/src/commands/cmd_function.cc index 8c38618e319..2123cc72ec7 100644 --- a/src/commands/cmd_function.cc +++ b/src/commands/cmd_function.cc @@ -61,6 +61,10 @@ struct CommandFunction : Commander { } return lua::FunctionListFunc(srv, funcname, output); + } else if (parser.EatEqICase("listlib")) { + auto libname = GET_OR_RET(parser.TakeStr().Prefixed("expect a library name")); + + return lua::FunctionListLib(srv, libname, output); } else if (parser.EatEqICase("delete")) { auto libname = GET_OR_RET(parser.TakeStr()); if (!lua::FunctionIsLibExist(conn, libname)) { diff --git a/src/storage/scripting.cc b/src/storage/scripting.cc index 5e0fa00e692..b0d63eb5584 100644 --- a/src/storage/scripting.cc +++ b/src/storage/scripting.cc @@ -383,6 +383,8 @@ bool FunctionIsLibExist(redis::Connection *conn, const std::string &libname, boo return static_cast(s); } +// FunctionCall will firstly find the function in the lua runtime, +// if it is not found, it will try to load the library where the function is located from storage Status FunctionCall(redis::Connection *conn, const std::string &name, const std::vector &keys, const std::vector &argv, std::string *output, bool read_only) { auto srv = conn->GetServer(); @@ -422,6 +424,7 @@ Status FunctionCall(redis::Connection *conn, const std::string &name, const std: return Status::OK(); } +// list all library names and their code (enabled via `with_code`) Status FunctionList(Server *srv, const std::string &libname, bool with_code, std::string *output) { std::string start_key = engine::kLuaLibCodePrefix + libname; std::string end_key = start_key; @@ -455,6 +458,8 @@ Status FunctionList(Server *srv, const std::string &libname, bool with_code, std return Status::OK(); } +// extension to Redis Function +// list all function names and their corresponding library names Status FunctionListFunc(Server *srv, const std::string &funcname, std::string *output) { std::string start_key = engine::kLuaFuncLibPrefix + funcname; std::string end_key = start_key; @@ -486,6 +491,47 @@ Status FunctionListFunc(Server *srv, const std::string &funcname, std::string *o return Status::OK(); } +// extension to Redis Function +// list detailed informantion of a specific library +// NOTE: it is required to load the library to lua runtime before listing (calling this function) +// i.e. it will output nothing if the library is only in storage but not loaded +Status FunctionListLib(Server *srv, const std::string &libname, std::string *output) { + auto lua = srv->Lua(); + + lua_getglobal(lua, REDIS_FUNCTION_LIBRARIES); + if (lua_isnil(lua, -1)) { + lua_pop(lua, 1); + lua_newtable(lua); + } + + lua_getfield(lua, -1, libname.c_str()); + if (lua_isnil(lua, -1)) { + lua_pop(lua, 2); + + return {Status::NotOK, "The library is not found or not loaded from storage"}; + } + + output->append(redis::MultiLen(6)); + output->append(redis::SimpleString("library_name")); + output->append(redis::SimpleString(libname)); + output->append(redis::SimpleString("engine")); + output->append(redis::SimpleString("lua")); + + auto count = lua_objlen(lua, -1); + output->append(redis::SimpleString("functions")); + output->append(redis::MultiLen(count)); + + for (size_t i = 1; i <= count; ++i) { + lua_rawgeti(lua, -1, static_cast(i)); + auto func = lua_tostring(lua, -1); + output->append(redis::SimpleString(func)); + lua_pop(lua, 1); + } + + lua_pop(lua, 2); + return Status::OK(); +} + Status FunctionDelete(Server *srv, const std::string &name) { auto lua = srv->Lua(); diff --git a/src/storage/scripting.h b/src/storage/scripting.h index 77df640e4aa..4c72f930353 100644 --- a/src/storage/scripting.h +++ b/src/storage/scripting.h @@ -68,6 +68,7 @@ Status FunctionCall(redis::Connection *conn, const std::string &name, const std: const std::vector &argv, std::string *output, bool read_only = false); Status FunctionList(Server *srv, const std::string &libname, bool with_code, std::string *output); Status FunctionListFunc(Server *srv, const std::string &funcname, std::string *output); +Status FunctionListLib(Server *srv, const std::string &libname, std::string *output); Status FunctionDelete(Server *srv, const std::string &name); bool FunctionIsLibExist(redis::Connection *conn, const std::string &libname, bool need_check_storage = true, bool read_only = false); diff --git a/tests/gocase/unit/scripting/function_test.go b/tests/gocase/unit/scripting/function_test.go index 98c90ef2e87..3e262db218a 100644 --- a/tests/gocase/unit/scripting/function_test.go +++ b/tests/gocase/unit/scripting/function_test.go @@ -160,4 +160,26 @@ func TestFunction(t *testing.T) { util.ErrorRegexp(t, rdb.Do(ctx, "FCALL_RO", "myset", 1, "x", 3).Err(), ".*Write commands are not allowed.*") }) + + t.Run("Restart server and test again", func(t *testing.T) { + srv.Restart() + + require.Equal(t, rdb.Do(ctx, "FCALL", "myget", 1, "x").Val(), "2") + require.Equal(t, rdb.Do(ctx, "FCALL", "hello", 0, "xxx").Val(), "Hello, xxx!") + + list := rdb.Do(ctx, "FUNCTION", "LIST").Val().([]interface{}) + require.Equal(t, list[1].(string), "mylib1") + require.Equal(t, list[3].(string), "mylib3") + require.Equal(t, len(list), 4) + }) + + t.Run("FUNCTION LISTLIB", func(t *testing.T) { + list := rdb.Do(ctx, "FUNCTION", "LISTLIB", "mylib1").Val().([]interface{}) + require.Equal(t, list[1].(string), "mylib1") + require.Equal(t, list[5].([]interface{}), []interface{}{"hello", "reverse"}) + + list = rdb.Do(ctx, "FUNCTION", "LISTLIB", "mylib3").Val().([]interface{}) + require.Equal(t, list[1].(string), "mylib3") + require.Equal(t, list[5].([]interface{}), []interface{}{"myget", "myset"}) + }) }