This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 493c17f8 Add LISTLIB subcommand as an extension to FUNCTION (#1796)
493c17f8 is described below

commit 493c17f841c1673a137e95b5235dec2d51ca6ab4
Author: Twice <[email protected]>
AuthorDate: Mon Oct 9 17:28:45 2023 +0900

    Add LISTLIB subcommand as an extension to FUNCTION (#1796)
    
    Co-authored-by: hulk <[email protected]>
---
 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 8c38618e..2123cc72 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 5e0fa00e..b0d63eb5 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<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();
@@ -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<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();
 
diff --git a/src/storage/scripting.h b/src/storage/scripting.h
index 77df640e..4c72f930 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<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);
diff --git a/tests/gocase/unit/scripting/function_test.go 
b/tests/gocase/unit/scripting/function_test.go
index 98c90ef2..3e262db2 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"})
+       })
 }

Reply via email to