Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LISTLIB subcommand as an extension to FUNCTION #1796

Merged
merged 2 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/commands/cmd_function.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
46 changes: 46 additions & 0 deletions src/storage/scripting.cc
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ bool FunctionIsLibExist(redis::Connection *conn, const std::string &libname, boo
return static_cast<bool>(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<std::string> &keys,
const std::vector<std::string> &argv, std::string *output, bool read_only) {
auto srv = conn->GetServer();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<int>(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();

Expand Down
1 change: 1 addition & 0 deletions src/storage/scripting.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Status FunctionCall(redis::Connection *conn, const std::string &name, const std:
const std::vector<std::string> &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);
Expand Down
22 changes: 22 additions & 0 deletions tests/gocase/unit/scripting/function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"})
})
}