You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kvrocks.apache.org by tw...@apache.org on 2023/10/09 08:28:52 UTC

[kvrocks] branch unstable updated: Add LISTLIB subcommand as an extension to FUNCTION (#1796)

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 <tw...@gmail.com>
AuthorDate: Mon Oct 9 17:28:45 2023 +0900

    Add LISTLIB subcommand as an extension to FUNCTION (#1796)
    
    Co-authored-by: hulk <hu...@gmail.com>
---
 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"})
+	})
 }