<HTML><BODY><div><div>C stored function always returns an array. Local call introduced<br>with box.func used to convert resulting array to multireturn in<br>contrast to netbox call that serializes it to a tuple.<br>This patch makes resulting behavior consistent by making the local<br>call always return a tuple as well.</div><div> </div><div>Closes #4799<br>---<br>Issue:<br><a href="https://github.com/tarantool/tarantool/issues/4799">https://github.com/tarantool/tarantool/issues/4799</a> <br>Branch:<br><a href="https://github.com/tarantool/tarantool/commit/7745bc83a44a28cbbe5ad55378caaa15bd7bb210">https://github.com/tarantool/tarantool/commit/7745bc83a44a28cbbe5ad55378caaa15bd7bb210</a> </div><div> </div><div> src/box/func.c                              |  2 +-<br> src/box/lua/misc.cc                         | 13 +++++++<br> src/box/port.c                              | 23 ++++++++++++<br> src/box/port.h                              |  4 +++<br> test/box/function1.result                   | 16 ++++-----<br> test/box/gh-4648-func-load-unload.result    |  2 ++<br> test/box/gh-4799-c-stored-function.result   | 40 +++++++++++++++++++++<br> test/box/gh-4799-c-stored-function.test.lua | 18 ++++++++++<br> 8 files changed, 109 insertions(+), 9 deletions(-)<br> create mode 100644 test/box/gh-4799-c-stored-function.result<br> create mode 100644 test/box/gh-4799-c-stored-function.test.lua</div><div>diff --git a/src/box/func.c b/src/box/func.c<br>index 8087c953f..be02c4b4d 100644<br>--- a/src/box/func.c<br>+++ b/src/box/func.c<br>@@ -567,7 +567,7 @@ func_c_call(struct func *base, struct port *args, struct port *ret)<br>     if (data == NULL)<br>         return -1;<br> <br>-    port_c_create(ret);<br>+    port_c_not_flat_create(ret);<br>     box_function_ctx_t ctx = { ret };<br> <br>     /* Module can be changed after function reload. */<br>diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc<br>index 5da84b35a..16c0e3ab9 100644<br>--- a/src/box/lua/misc.cc<br>+++ b/src/box/lua/misc.cc<br>@@ -80,6 +80,19 @@ port_c_dump_lua(struct port *base, struct lua_State *L, bool is_flat)<br>     }<br> }<br> <br>+/**<br>+ * Third parameter (is_flat) should always be considered<br>+ * false because there is no way to detect whether dumped<br>+ * result is tuple or multireturn. That ensures behavior<br>+ * that is consistent with msgpack_dump which encodes<br>+ * returned tuple as an array.<br>+ */<br>+extern "C" void<br>+port_c_dump_lua_not_flat(struct port *base, struct lua_State *L, bool)<br>+{<br>+    port_c_dump_lua(base, L, false);<br>+}<br>+<br> extern "C" void<br> port_msgpack_dump_lua(struct port *base, struct lua_State *L, bool is_flat)<br> {<br>diff --git a/src/box/port.c b/src/box/port.c<br>index e9e1bda7a..845061f29 100644<br>--- a/src/box/port.c<br>+++ b/src/box/port.c<br>@@ -203,6 +203,9 @@ port_c_dump_msgpack(struct port *base, struct obuf *out)<br> extern void<br> port_c_dump_lua(struct port *port, struct lua_State *L, bool is_flat);<br> <br>+extern void<br>+port_c_dump_lua_not_flat(struct port *port, struct lua_State *L, bool is_flat);<br>+<br> extern struct sql_value *<br> port_c_get_vdbemem(struct port *base, uint32_t *size);<br> <br>@@ -216,6 +219,26 @@ const struct port_vtab port_c_vtab = {<br>     .destroy = port_c_destroy,<br> };<br> <br>+const struct port_vtab port_c_not_flat_vtab = {<br>+    .dump_msgpack = port_c_dump_msgpack,<br>+    .dump_msgpack_16 = port_c_dump_msgpack_16,<br>+    .dump_lua = port_c_dump_lua_not_flat,<br>+    .dump_plain = NULL,<br>+    .get_msgpack = NULL,<br>+    .get_vdbemem = port_c_get_vdbemem,<br>+    .destroy = port_c_destroy,<br>+};<br>+<br>+void<br>+port_c_not_flat_create(struct port *base)<br>+{<br>+    struct port_c *port = (struct port_c *)base;<br>+    port->vtab = &port_c_not_flat_vtab;<br>+    port->first = NULL;<br>+    port->last = NULL;<br>+    port->size = 0;<br>+}<br>+<br> void<br> port_c_create(struct port *base)<br> {<br>diff --git a/src/box/port.h b/src/box/port.h<br>index 43d0f9deb..9873742b5 100644<br>--- a/src/box/port.h<br>+++ b/src/box/port.h<br>@@ -140,6 +140,10 @@ static_assert(sizeof(struct port_c) <= sizeof(struct port),<br> void<br> port_c_create(struct port *base);<br> <br>+/** Create a C port object. (always not flat) */<br>+void<br>+port_c_not_flat_create(struct port *base);<br>+<br> /** Append a tuple to the port. Tuple is referenced. */<br> int<br> port_c_add_tuple(struct port *port, struct tuple *tuple);<br>diff --git a/test/box/function1.result b/test/box/function1.result<br>index 905a4cdab..a8e33e601 100644<br>--- a/test/box/function1.result<br>+++ b/test/box/function1.result<br>@@ -114,7 +114,7 @@ box.func["function1.args"]:call({ "xx" })<br> ...<br> box.func["function1.args"]:call({ 15 })<br> ---<br>-- [15, 'hello']<br>+- - [15, 'hello']<br> ...<br> box.schema.func.drop("function1.args")<br> ---<br>@@ -645,11 +645,11 @@ func:call({4})<br> ...<br> func:call({4, 2})<br> ---<br>-- [2]<br>+- - [2]<br> ...<br> func:call({4, 2, 1})<br> ---<br>-- [2]<br>+- - [2]<br> ...<br> func:drop()<br> ---<br>@@ -802,11 +802,11 @@ box.schema.func.create(name, {language = "C", exports = {'LUA'}})<br> ...<br> box.func[name]:call()<br> ---<br>-- 1<br>-- -1<br>-- 18446744073709551615<br>-- '123456789101112131415'<br>-- [2]<br>+- - 1<br>+  - -1<br>+  - 18446744073709551615<br>+  - '123456789101112131415'<br>+  - [2]<br> ...<br> box.schema.user.grant('guest', 'super')<br> ---<br>diff --git a/test/box/gh-4648-func-load-unload.result b/test/box/gh-4648-func-load-unload.result<br>index e695dd365..3fa7eb9dc 100644<br>--- a/test/box/gh-4648-func-load-unload.result<br>+++ b/test/box/gh-4648-func-load-unload.result<br>@@ -59,6 +59,7 @@ check_module_count_diff(0)<br>  | ...<br> box.func.function1:call()<br>  | ---<br>+ | - []<br>  | ...<br> check_module_count_diff(1)<br>  | ---<br>@@ -100,6 +101,7 @@ end)<br>  | ...<br> box.func.function1:call()<br>  | ---<br>+ | - []<br>  | ...<br> check_module_count_diff(1)<br>  | ---<br>diff --git a/test/box/gh-4799-c-stored-function.result b/test/box/gh-4799-c-stored-function.result<br>new file mode 100644<br>index 000000000..b6df3d17a<br>--- /dev/null<br>+++ b/test/box/gh-4799-c-stored-function.result<br>@@ -0,0 +1,40 @@<br>+-- test-run result file version 2<br>+--<br>+-- gh-4799: C stored function's output used to be inconsistent:<br>+-- multireturn if called locally, a tuple if called via netbox.<br>+-- The latter was chosen as preferred behavior because it existed<br>+-- long before local call was introduced in box.func.<br>+--<br>+build_path = os.getenv("BUILDDIR")<br>+ | ---<br>+ | ...<br>+package.cpath = build_path..'/test/box/?.so;'..build_path..'/test/box/?.dylib;'..package.cpath<br>+ | ---<br>+ | ...<br>+box.schema.func.create('function1.multireturn', {language = "C", exports = {'LUA'}})<br>+ | ---<br>+ | ...<br>+box.schema.user.grant('guest', 'super')<br>+ | ---<br>+ | ...<br>+c = require('net.box').connect(box.cfg.listen)<br>+ | ---<br>+ | ...<br>+<br>+-- Both calls should return a tuple now.<br>+c:call('function1.multireturn')<br>+ | ---<br>+ | - [[1], [1]]<br>+ | ...<br>+box.func['function1.multireturn']:call()<br>+ | ---<br>+ | - - [1]<br>+ |   - [1]<br>+ | ...<br>+<br>+box.schema.user.revoke('guest', 'super')<br>+ | ---<br>+ | ...<br>+box.schema.func.drop('function1.multireturn')<br>+ | ---<br>+ | ...<br>diff --git a/test/box/gh-4799-c-stored-function.test.lua b/test/box/gh-4799-c-stored-function.test.lua<br>new file mode 100644<br>index 000000000..d0b3b942f<br>--- /dev/null<br>+++ b/test/box/gh-4799-c-stored-function.test.lua<br>@@ -0,0 +1,18 @@<br>+--<br>+-- gh-4799: C stored function's output used to be inconsistent:<br>+-- multireturn if called locally, a tuple if called via netbox.<br>+-- The latter was chosen as preferred behavior because it existed<br>+-- long before local call was introduced in box.func.<br>+--<br>+build_path = os.getenv("BUILDDIR")<br>+package.cpath = build_path..'/test/box/?.so;'..build_path..'/test/box/?.dylib;'..package.cpath<br>+box.schema.func.create('function1.multireturn', {language = "C", exports = {'LUA'}})<br>+box.schema.user.grant('guest', 'super')<br>+c = require('net.box').connect(box.cfg.listen)<br>+<br>+-- Both calls should return a tuple now.<br>+c:call('function1.multireturn')<br>+box.func['function1.multireturn']:call()<br>+<br>+box.schema.user.revoke('guest', 'super')<br>+box.schema.func.drop('function1.multireturn')<br>-- <br>2.24.0<br> </div></div><div> </div><div data-signature-widget="container"><div data-signature-widget="content"><div>--<br>Maria Khaydich</div></div></div></BODY></HTML>