From d462c77c0202c6e0b47339182757f6cf99670d84 Mon Sep 17 00:00:00 2001
From: Vladimir Davydov <vdavydov@tarantool.org>
Date: Mon, 27 Nov 2023 15:12:38 +0300
Subject: [PATCH] net.box: allow calling stored Lua and C module functions with
 self.call

The fix is simple: look up the function in `box.func` by name and, if
found, execute its `call` method. The only tricky part is to avoid the
lookup before `box.cfg` is called because `box.func` is unavailable at
the time. We achieve that by checking `box.ctl.is_recovery_finished`.

Closes #9131

NO_DOC=bug fix

(cherry picked from commit e92a8e7b20dc7e2ab44afe7391ff46973acf0bcf)
---
 .../gh-9131-net-box-self-call-stored-func.md  |   4 +
 src/box/lua/net_box.lua                       |   6 +
 test/box-luatest/CMakeLists.txt               |   2 +
 .../gh_9131_net_box_self_call_stored_func.c   |  25 ++++
 ...131_net_box_self_call_stored_func_test.lua | 125 ++++++++++++++++++
 5 files changed, 162 insertions(+)
 create mode 100644 changelogs/unreleased/gh-9131-net-box-self-call-stored-func.md
 create mode 100644 test/box-luatest/gh_9131_net_box_self_call_stored_func.c
 create mode 100644 test/box-luatest/gh_9131_net_box_self_call_stored_func_test.lua

diff --git a/changelogs/unreleased/gh-9131-net-box-self-call-stored-func.md b/changelogs/unreleased/gh-9131-net-box-self-call-stored-func.md
new file mode 100644
index 0000000000..e6f84ee8b1
--- /dev/null
+++ b/changelogs/unreleased/gh-9131-net-box-self-call-stored-func.md
@@ -0,0 +1,4 @@
+## bugfix/net.box
+
+* It is now possible to call stored Lua functions and C module functions with
+  `require('net.box').self:call()` (gh-9131).
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index e7a5bd3cd3..1dbbe14887 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -1296,6 +1296,12 @@ this_module.self = {
         check_call_args(args)
         args = args or {}
         proc_name = tostring(proc_name)
+        if box.ctl.is_recovery_finished() then
+            local f = box.func[proc_name]
+            if f ~= nil then
+                return handle_eval_result(pcall(f.call, f, args))
+            end
+        end
         local status, proc, obj = pcall(box.internal.call_loadproc, proc_name)
         if not status then
             rollback()
diff --git a/test/box-luatest/CMakeLists.txt b/test/box-luatest/CMakeLists.txt
index 368a777721..953fcb8378 100644
--- a/test/box-luatest/CMakeLists.txt
+++ b/test/box-luatest/CMakeLists.txt
@@ -2,3 +2,5 @@ include_directories(${MSGPUCK_INCLUDE_DIRS})
 build_module(gh_4799_lib gh_4799_fix_c_stored_functions_call.c)
 target_link_libraries(gh_4799_lib msgpuck)
 build_module(gh_6506_lib gh_6506_wakeup_writing_to_wal_fiber.c)
+build_module(gh_9131_lib gh_9131_net_box_self_call_stored_func.c)
+target_link_libraries(gh_9131_lib msgpuck)
diff --git a/test/box-luatest/gh_9131_net_box_self_call_stored_func.c b/test/box-luatest/gh_9131_net_box_self_call_stored_func.c
new file mode 100644
index 0000000000..c0d9142819
--- /dev/null
+++ b/test/box-luatest/gh_9131_net_box_self_call_stored_func.c
@@ -0,0 +1,25 @@
+#include "module.h"
+#include "msgpuck.h"
+
+int
+c_func_echo(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	const char *data = args;
+	uint32_t count = mp_decode_array(&data);
+	for (uint32_t i = 0; i < count; i++) {
+		const char *data_end = data;
+		mp_next(&data_end);
+		box_return_mp(ctx, data, data_end);
+		data = data_end;
+	}
+	return 0;
+}
+
+int
+c_func_error(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	(void)ctx;
+	(void)args;
+	(void)args_end;
+	return box_error_raise(ER_PROC_LUA, "test");
+}
diff --git a/test/box-luatest/gh_9131_net_box_self_call_stored_func_test.lua b/test/box-luatest/gh_9131_net_box_self_call_stored_func_test.lua
new file mode 100644
index 0000000000..e78b54d229
--- /dev/null
+++ b/test/box-luatest/gh_9131_net_box_self_call_stored_func_test.lua
@@ -0,0 +1,125 @@
+local server = require('luatest.server')
+local t = require('luatest')
+
+local g = t.group('gh_9131')
+
+g.before_all(function(cg)
+    cg.server = server:new()
+    cg.server:start()
+    cg.server:exec(function()
+        package.cpath = ('%s/test/box-luatest/?.%s;%s'):format(
+            os.getenv('BUILDDIR'), jit.os == 'OSX' and 'dylib' or 'so',
+            package.path)
+        box.schema.func.create('gh_9131_lib.c_func_echo', {language = 'c'})
+        box.schema.func.create('gh_9131_lib.c_func_error', {language = 'c'})
+        box.schema.func.create('gh_9131_lib.c_func_undef', {language = 'c'})
+        box.schema.func.create('c_func_undef', {language = 'c'})
+        rawset(_G, 'lua_func_echo', function(...) return ... end)
+        box.schema.func.create('lua_func_echo')
+        rawset(_G, 'lua_func_error', function() return error('test') end)
+        box.schema.func.create('lua_func_error')
+        box.schema.func.create('lua_func_undef')
+        box.schema.func.create('stored_lua_func_echo', {
+            body = [[function(...) return ... end]],
+        })
+        box.schema.func.create('stored_lua_func_error', {
+            body = [[function() return error('test') end]],
+        })
+        rawset(_G, 'unreg_lua_func_echo',
+               function(...) return ... end)
+        rawset(_G, 'unreg_lua_func_error',
+               function() return error('test') end)
+    end)
+end)
+
+g.after_all(function(cg)
+    cg.server:drop()
+    cg.server = nil
+end)
+
+g.test_c_func = function(cg)
+    cg.server:exec(function()
+        local self = require('net.box').self
+        t.assert_equals({self:call('gh_9131_lib.c_func_echo')}, {})
+        t.assert_equals({self:call('gh_9131_lib.c_func_echo', {})}, {})
+        t.assert_equals({self:call('gh_9131_lib.c_func_echo', {1})}, {1})
+        t.assert_equals({self:call('gh_9131_lib.c_func_echo', {1LL})}, {1})
+        t.assert_equals({self:call('gh_9131_lib.c_func_echo', {1, 2, 3})},
+                        {1, 2, 3})
+        t.assert_error_msg_equals('test', self.call, self,
+                                  'gh_9131_lib.c_func_error')
+        t.assert_error_msg_equals(
+            "Procedure 'gh_9131.c_func_undef' is not defined",
+            self.call, self, 'gh_9131.c_func_undef')
+        t.assert_error_msg_equals(
+            "Failed to dynamically load module 'c_func_undef': " ..
+            "module not found", self.call, self, 'c_func_undef')
+    end)
+end
+
+g.test_lua_func = function(cg)
+    cg.server:exec(function()
+        local self = require('net.box').self
+        t.assert_equals({self:call('lua_func_echo')}, {})
+        t.assert_equals({self:call('lua_func_echo', {})}, {})
+        t.assert_equals({self:call('lua_func_echo', {1})}, {1})
+        t.assert_equals({self:call('lua_func_echo', {1LL})}, {1})
+        t.assert_equals({self:call('lua_func_echo', {1, 2, 3})}, {1, 2, 3})
+        t.assert_error_msg_equals('test', self.call, self, 'lua_func_error')
+        t.assert_error_msg_equals("Procedure 'lua_func_undef' is not defined",
+                                  self.call, self, 'lua_func_undef')
+    end)
+end
+
+g.test_stored_lua_func = function(cg)
+    cg.server:exec(function()
+        local self = require('net.box').self
+        t.assert_equals({self:call('stored_lua_func_echo')}, {})
+        t.assert_equals({self:call('stored_lua_func_echo', {})}, {})
+        t.assert_equals({self:call('stored_lua_func_echo', {1})}, {1})
+        t.assert_equals({self:call('stored_lua_func_echo', {1LL})}, {1})
+        t.assert_equals({self:call('stored_lua_func_echo', {1, 2, 3})},
+                        {1, 2, 3})
+        t.assert_error_msg_equals('test', self.call, self,
+                                  'stored_lua_func_error')
+    end)
+end
+
+g.test_unreg_lua_func = function(cg)
+    cg.server:exec(function()
+        local self = require('net.box').self
+        t.assert_equals({self:call('unreg_lua_func_echo')}, {})
+        t.assert_equals({self:call('unreg_lua_func_echo', {})}, {})
+        t.assert_equals({self:call('unreg_lua_func_echo', {1})}, {1})
+        t.assert_equals({self:call('unreg_lua_func_echo', {1LL})}, {1})
+        t.assert_equals({self:call('unreg_lua_func_echo', {1, 2, 3})},
+                        {1, 2, 3})
+        t.assert_error_msg_equals('test', self.call, self,
+                                  'unreg_lua_func_error')
+        t.assert_error_msg_equals(
+            "Procedure 'unreg_lua_func_undef' is not defined",
+            self.call, self, 'unreg_lua_func_undef')
+    end)
+end
+
+local g_local = t.group('gh_9131.local')
+
+g_local.before_all(function()
+    rawset(_G, 'local_func_echo', function(...) return ... end)
+    rawset(_G, 'local_func_error', function() return error('test') end)
+end)
+
+g_local.after_all(function()
+    rawset(_G, 'local_func_echo', nil)
+    rawset(_G, 'local_func_error', nil)
+end)
+
+g_local.test_box_not_configured = function()
+    local self = require('net.box').self
+    t.assert_equals({self:call('local_func_echo')}, {})
+    t.assert_equals({self:call('local_func_echo', {})}, {})
+    t.assert_equals({self:call('local_func_echo', {1})}, {1})
+    t.assert_equals({self:call('local_func_echo', {1LL})}, {1})
+    t.assert_equals({self:call('local_func_echo', {1, 2, 3})}, {1, 2, 3})
+    t.assert_error_msg_equals('test', self.call, self, 'local_func_error')
+end
-- 
GitLab