[PATCH v4 4/6] box: export registered functions in box.func folder
Kirill Shcherbatov
kshcherbatov at tarantool.org
Sun Jun 23 16:57:55 MSK 2019
Needed for #4182, #1260
@TarantoolBot document
Title: Export registered functions to box.func folder
Now all registered with box.schema.func.create functions are
exported in box.func folder.
Each function have :call and :drop method. The :drop method
just a shortcut for box.schema.func.drop interface.
The :call method is similar to net.box connection:call method
and allows to call a registered function directly.
All access checks are performed on each function call.
Example:
function sum(a, b) return a + b end
box.schema.func.create('sum')
box.func.sum
---
- language: LUA
setuid: false
name: sum
id: 2
...
box.func.sum:call({1, 3})
---
- 4
...
box.func.sum:drop()
---
src/box/alter.cc | 2 +
src/box/lua/call.c | 247 ++++++++++++++++++++++++++++++++----
src/box/lua/init.c | 1 +
src/box/lua/misc.cc | 8 +-
src/box/lua/schema.lua | 29 +++++
src/box/port.h | 2 -
src/box/schema.cc | 1 +
src/box/schema.h | 5 +
test/box/function1.c | 33 +++++
test/box/function1.result | 215 +++++++++++++++++++++++++++++++
test/box/function1.test.lua | 69 ++++++++++
test/box/misc.result | 1 +
12 files changed, 581 insertions(+), 32 deletions(-)
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 32c4b566a..33f9b0a71 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2583,6 +2583,7 @@ func_cache_remove_func(struct trigger *trigger, void * /* event */)
{
struct func *old_func = (struct func *) trigger->data;
func_cache_delete(old_func->def->fid);
+ trigger_run_xc(&on_alter_func, old_func);
func_delete(old_func);
}
@@ -2615,6 +2616,7 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
def_guard.is_active = false;
func_cache_insert(func);
on_rollback->data = func;
+ trigger_run_xc(&on_alter_func, func);
txn_on_rollback(txn, on_rollback);
} else if (new_tuple == NULL) { /* DELETE */
uint32_t uid;
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 8b7223a7c..8c70c1088 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -33,10 +33,13 @@
#include "box/error.h"
#include "box/func.h"
#include "box/func_def.h"
+#include "box/schema.h"
#include "fiber.h"
+#include "tt_static.h"
#include "lua/utils.h"
#include "lua/msgpack.h"
+#include "lua/trigger.h"
#include "box/port.h"
#include "box/lua/tuple.h"
@@ -284,8 +287,13 @@ port_lua_create(struct port *port, struct lua_State *L)
}
struct execute_lua_ctx {
- const char *name;
- uint32_t name_len;
+ union {
+ struct {
+ const char *name;
+ uint32_t name_len;
+ };
+ struct mpstream *stream;
+ };
struct port *args;
};
@@ -340,58 +348,56 @@ execute_lua_eval(lua_State *L)
static int
encode_lua_call(lua_State *L)
{
- struct port_lua *port = (struct port_lua *) lua_topointer(L, -1);
+ struct execute_lua_ctx *ctx =
+ (struct execute_lua_ctx *) lua_topointer(L, 1);
/*
* Add all elements from Lua stack to the buffer.
*
* TODO: forbid explicit yield from __serialize or __index here
*/
- struct mpstream stream;
- mpstream_init(&stream, port->out, obuf_reserve_cb, obuf_alloc_cb,
- luamp_error, port->L);
-
struct luaL_serializer *cfg = luaL_msgpack_default;
+ struct port_lua *port = (struct port_lua *) ctx->args;
int size = lua_gettop(port->L);
for (int i = 1; i <= size; ++i)
- luamp_encode(port->L, cfg, &stream, i);
+ luamp_encode(port->L, cfg, ctx->stream, i);
port->size = size;
- mpstream_flush(&stream);
+ mpstream_flush(ctx->stream);
return 0;
}
static int
encode_lua_call_16(lua_State *L)
{
- struct port_lua *port = (struct port_lua *) lua_topointer(L, -1);
+ struct execute_lua_ctx *ctx =
+ (struct execute_lua_ctx *) lua_topointer(L, 1);
/*
* Add all elements from Lua stack to the buffer.
*
* TODO: forbid explicit yield from __serialize or __index here
*/
- struct mpstream stream;
- mpstream_init(&stream, port->out, obuf_reserve_cb, obuf_alloc_cb,
- luamp_error, port->L);
-
struct luaL_serializer *cfg = luaL_msgpack_default;
- port->size = luamp_encode_call_16(port->L, cfg, &stream);
- mpstream_flush(&stream);
+ struct port_lua *port = (struct port_lua *) ctx->args;
+ port->size = luamp_encode_call_16(port->L, cfg, ctx->stream);
+ mpstream_flush(ctx->stream);
return 0;
}
static inline int
-port_lua_do_dump(struct port *base, struct obuf *out, lua_CFunction handler)
+port_lua_do_dump(struct port *base, struct mpstream *stream,
+ lua_CFunction handler)
{
- struct port_lua *port = (struct port_lua *)base;
+ struct port_lua *port = (struct port_lua *) base;
assert(port->vtab == &port_lua_vtab);
- /* Use port to pass arguments to encoder quickly. */
- port->out = out;
/*
* Use the same global state, assuming the encoder doesn't
* yield.
*/
+ struct execute_lua_ctx ctx;
+ ctx.args = base;
+ ctx.stream = stream;
struct lua_State *L = tarantool_L;
int top = lua_gettop(L);
- if (lua_cpcall(L, handler, port) != 0) {
+ if (lua_cpcall(L, handler, &ctx) != 0) {
luaT_toerror(port->L);
return -1;
}
@@ -402,13 +408,57 @@ port_lua_do_dump(struct port *base, struct obuf *out, lua_CFunction handler)
static int
port_lua_dump(struct port *base, struct obuf *out)
{
- return port_lua_do_dump(base, out, encode_lua_call);
+ struct port_lua *port = (struct port_lua *) base;
+ struct mpstream stream;
+ mpstream_init(&stream, out, obuf_reserve_cb, obuf_alloc_cb,
+ luamp_error, port->L);
+ return port_lua_do_dump(base, &stream, encode_lua_call);
}
static int
port_lua_dump_16(struct port *base, struct obuf *out)
{
- return port_lua_do_dump(base, out, encode_lua_call_16);
+ struct port_lua *port = (struct port_lua *)base;
+ struct mpstream stream;
+ mpstream_init(&stream, out, obuf_reserve_cb, obuf_alloc_cb,
+ luamp_error, port->L);
+ return port_lua_do_dump(base, &stream, encode_lua_call_16);
+}
+
+static void
+port_lua_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
+{
+ (void) is_flat;
+ assert(is_flat == true);
+ struct port_lua *port = (struct port_lua *) base;
+ uint32_t size = lua_gettop(port->L);
+ lua_xmove(port->L, L, size);
+ port->size = size;
+}
+
+static const char *
+port_lua_get_msgpack(struct port *base, uint32_t *size)
+{
+ struct port_lua *port = (struct port_lua *) base;
+ struct region *region = &fiber()->gc;
+ uint32_t region_svp = region_used(region);
+ struct mpstream stream;
+ mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb,
+ luamp_error, port->L);
+ mpstream_encode_array(&stream, lua_gettop(port->L));
+ int rc = port_lua_do_dump(base, &stream, encode_lua_call);
+ if (rc < 0) {
+ region_truncate(region, region_svp);
+ return NULL;
+ }
+ *size = region_used(region) - region_svp;
+ const char *data = region_join(region, *size);
+ if (data == NULL) {
+ diag_set(OutOfMemory, *size, "region", "data");
+ region_truncate(region, region_svp);
+ return NULL;
+ }
+ return data;
}
static void
@@ -429,9 +479,9 @@ port_lua_dump_plain(struct port *port, uint32_t *size);
static const struct port_vtab port_lua_vtab = {
.dump_msgpack = port_lua_dump,
.dump_msgpack_16 = port_lua_dump_16,
- .dump_lua = NULL,
+ .dump_lua = port_lua_dump_lua,
.dump_plain = port_lua_dump_plain,
- .get_msgpack = NULL,
+ .get_msgpack = port_lua_get_msgpack,
.destroy = port_lua_destroy,
};
@@ -527,10 +577,150 @@ lbox_module_reload(lua_State *L)
return 0;
}
+int
+lbox_func_call(struct lua_State *L)
+{
+ if (lua_gettop(L) < 1 || !lua_isstring(L, 1))
+ return luaL_error(L, "Use func:call(...)");
+
+ size_t name_len;
+ const char *name = lua_tolstring(L, 1, &name_len);
+ struct func *func = func_by_name(name, name_len);
+ if (func == NULL) {
+ diag_set(ClientError, ER_NO_SUCH_FUNCTION,
+ tt_cstr(name, name_len));
+ return luaT_error(L);
+ }
+
+ /*
+ * Prepare a new Lua stack for input arguments
+ * before the function call to pass it into the
+ * pcall-sandboxed tarantool_L handler.
+ */
+ lua_State *args_L = lua_newthread(tarantool_L);
+ int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
+ lua_xmove(L, args_L, lua_gettop(L) - 1);
+ struct port args;
+ port_lua_create(&args, args_L);
+ ((struct port_lua *) &args)->ref = coro_ref;
+
+ struct port ret;
+ if (func_call(func, &args, &ret) != 0) {
+ port_destroy(&args);
+ return luaT_error(L);
+ }
+
+ int top = lua_gettop(L);
+ port_dump_lua(&ret, L, true);
+ int cnt = lua_gettop(L) - top;
+
+ port_destroy(&ret);
+ port_destroy(&args);
+ return cnt;
+}
+
+static void
+lbox_func_new(struct lua_State *L, struct func *func)
+{
+ lua_getfield(L, LUA_GLOBALSINDEX, "box");
+ lua_getfield(L, -1, "func");
+ if (!lua_istable(L, -1)) {
+ lua_pop(L, 1); /* pop nil */
+ lua_newtable(L);
+ lua_setfield(L, -2, "func");
+ lua_getfield(L, -1, "func");
+ }
+ lua_rawgeti(L, -1, func->def->fid);
+ if (lua_isnil(L, -1)) {
+ /*
+ * If the function already exists, modify it,
+ * rather than create a new one -- to not
+ * invalidate Lua variable references to old func
+ * outside the box.schema.func[].
+ */
+ lua_pop(L, 1);
+ lua_newtable(L);
+ lua_rawseti(L, -2, func->def->fid);
+ lua_rawgeti(L, -1, func->def->fid);
+ } else {
+ /* Clear the reference to old func by old name. */
+ lua_getfield(L, -1, "name");
+ lua_pushnil(L);
+ lua_settable(L, -4);
+ }
+ int top = lua_gettop(L);
+ lua_pushstring(L, "id");
+ lua_pushnumber(L, func->def->fid);
+ lua_settable(L, top);
+ lua_pushstring(L, "name");
+ lua_pushstring(L, func->def->name);
+ lua_settable(L, top);
+ lua_pushstring(L, "setuid");
+ lua_pushboolean(L, func->def->setuid);
+ lua_settable(L, top);
+ lua_pushstring(L, "language");
+ lua_pushstring(L, func_language_strs[func->def->language]);
+ lua_settable(L, top);
+
+ /* Bless func object. */
+ lua_getfield(L, LUA_GLOBALSINDEX, "box");
+ lua_pushstring(L, "schema");
+ lua_gettable(L, -2);
+ lua_pushstring(L, "func");
+ lua_gettable(L, -2);
+ lua_pushstring(L, "bless");
+ lua_gettable(L, -2);
+
+ lua_pushvalue(L, top);
+ lua_call(L, 1, 0);
+ lua_pop(L, 3);
+
+ lua_setfield(L, -2, func->def->name);
+
+ lua_pop(L, 2);
+}
+
+static void
+lbox_func_delete(struct lua_State *L, struct func *func)
+{
+ uint32_t fid = func->def->fid;
+ lua_getfield(L, LUA_GLOBALSINDEX, "box");
+ lua_getfield(L, -1, "func");
+ assert(!lua_isnil(L, -1));
+ lua_rawgeti(L, -1, fid);
+ if (!lua_isnil(L, -1)) {
+ lua_getfield(L, -1, "name");
+ lua_pushnil(L);
+ lua_rawset(L, -4);
+ lua_pop(L, 1); /* pop func */
+ lua_pushnil(L);
+ lua_rawseti(L, -2, fid);
+ } else {
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 2); /* box, func */
+}
+
+static void
+lbox_func_new_or_delete(struct trigger *trigger, void *event)
+{
+ struct lua_State *L = (struct lua_State *) trigger->data;
+ struct func *func = (struct func *)event;
+ if (func != NULL)
+ lbox_func_new(L, func);
+ else
+ lbox_func_delete(L, func);
+}
+
+static struct trigger on_alter_func_in_lua = {
+ RLIST_LINK_INITIALIZER, lbox_func_new_or_delete, NULL, NULL
+};
+
static const struct luaL_Reg boxlib_internal[] = {
{"call_loadproc", lbox_call_loadproc},
{"sql_create_function", lbox_sql_create_function},
{"module_reload", lbox_module_reload},
+ {"func_call", lbox_func_call},
{NULL, NULL}
};
@@ -539,7 +729,12 @@ box_lua_call_init(struct lua_State *L)
{
luaL_register(L, "box.internal", boxlib_internal);
lua_pop(L, 1);
-
+ /*
+ * Register the trigger that will push persistent
+ * Lua functions objects to Lua.
+ */
+ on_alter_func_in_lua.data = L;
+ trigger_add(&on_alter_func, &on_alter_func_in_lua);
#if 0
/* Get CTypeID for `struct port *' */
int rc = luaL_cdef(L, "struct port;");
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index 76b987b4b..7ffed409d 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -39,6 +39,7 @@
#include "box/box.h"
#include "box/txn.h"
+#include "box/func.h"
#include "box/vclock.h"
#include "box/lua/error.h"
diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc
index de9ed407b..54f1d7a58 100644
--- a/src/box/lua/misc.cc
+++ b/src/box/lua/misc.cc
@@ -67,14 +67,14 @@ lbox_encode_tuple_on_gc(lua_State *L, int idx, size_t *p_len)
extern "C" void
port_tuple_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
{
- (void) is_flat;
- assert(is_flat == false);
struct port_tuple *port = port_tuple(base);
- lua_createtable(L, port->size, 0);
+ if (!is_flat)
+ lua_createtable(L, port->size, 0);
struct port_tuple_entry *pe = port->first;
for (int i = 0; pe != NULL; pe = pe->next) {
luaT_pushtuple(L, pe->tuple);
- lua_rawseti(L, -2, ++i);
+ if (!is_flat)
+ lua_rawseti(L, -2, ++i);
}
}
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 3f080eced..9c3ee063c 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -2190,7 +2190,36 @@ function box.schema.func.exists(name_or_id)
return tuple ~= nil
end
+-- Helper function to check func:method() usage
+local function check_func_arg(func, method)
+ if type(func) ~= 'table' or func.name == nil then
+ local fmt = 'Use func:%s(...) instead of func.%s(...)'
+ error(string.format(fmt, method, method))
+ end
+end
+
+local func_mt = {}
+
+func_mt.drop = function(func, opts)
+ check_func_arg(func, 'drop')
+ box.schema.func.drop(func.name, opts)
+end
+
+func_mt.call = function(func, args)
+ check_func_arg(func, 'call')
+ args = args or {}
+ if type(args) ~= 'table' then
+ error('Use func:call(table)')
+ end
+ return box.schema.func.call(func.name, unpack(args))
+end
+
+function box.schema.func.bless(func)
+ setmetatable(func, {__index = func_mt})
+end
+
box.schema.func.reload = internal.module_reload
+box.schema.func.call = internal.func_call
box.internal.collation = {}
box.internal.collation.create = function(name, coll_type, locale, opts)
diff --git a/src/box/port.h b/src/box/port.h
index db93f8eea..a7f5d81bd 100644
--- a/src/box/port.h
+++ b/src/box/port.h
@@ -102,8 +102,6 @@ struct port_lua {
struct lua_State *L;
/** Reference to L in tarantool_L. */
int ref;
- /** The argument of port_dump */
- struct obuf *out;
/** Number of entries dumped to the port. */
int size;
};
diff --git a/src/box/schema.cc b/src/box/schema.cc
index d63add535..bf0ed33b7 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -72,6 +72,7 @@ uint32_t space_cache_version = 0;
struct rlist on_schema_init = RLIST_HEAD_INITIALIZER(on_schema_init);
struct rlist on_alter_space = RLIST_HEAD_INITIALIZER(on_alter_space);
struct rlist on_alter_sequence = RLIST_HEAD_INITIALIZER(on_alter_sequence);
+struct rlist on_alter_func = RLIST_HEAD_INITIALIZER(on_alter_func);
/**
* Lock of scheme modification
diff --git a/src/box/schema.h b/src/box/schema.h
index 7e8ac6c11..f0039b29d 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -237,6 +237,11 @@ extern struct rlist on_alter_sequence;
*/
extern struct rlist on_access_denied;
+/**
+ * Triggers fired after committing a change in _func space.
+ */
+extern struct rlist on_alter_func;
+
/**
* Context passed to on_access_denied trigger.
*/
diff --git a/test/box/function1.c b/test/box/function1.c
index 053e4fe91..ee5a422b5 100644
--- a/test/box/function1.c
+++ b/test/box/function1.c
@@ -42,6 +42,39 @@ args(box_function_ctx_t *ctx, const char *args, const char *args_end)
return box_return_tuple(ctx, tuple);
}
+int
+divide(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+ uint32_t arg_count = mp_decode_array(&args);
+ if (arg_count < 2)
+ goto error;
+
+ if (mp_typeof(*args) != MP_UINT)
+ goto error;
+ double a = mp_decode_uint(&args);
+ if (mp_typeof(*args) != MP_UINT)
+ goto error;
+ double b = mp_decode_uint(&args);
+ if (b == 0)
+ goto error;
+
+ char tuple_buf[512];
+ char *d = tuple_buf;
+ d = mp_encode_array(d, 1);
+ d = mp_encode_double(d, a / b);
+ assert(d <= tuple_buf + sizeof(tuple_buf));
+
+ box_tuple_format_t *fmt = box_tuple_format_default();
+ box_tuple_t *tuple = box_tuple_new(fmt, tuple_buf, d);
+ if (tuple == NULL)
+ return -1;
+ return box_return_tuple(ctx, tuple);
+error:
+ return box_error_set(__FILE__, __LINE__, ER_PROC_C, "%s",
+ "invalid argument");
+}
+
+
/*
* For each UINT key in arguments create or increment counter in
* box.space.test space.
diff --git a/test/box/function1.result b/test/box/function1.result
index cadeb0467..99006926e 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -57,6 +57,25 @@ c:call('function1.args', { 15 })
---
- [[15, 'hello']]
...
+box.func["function1.args"]
+---
+- language: C
+ setuid: false
+ name: function1.args
+ id: 2
+...
+box.func["function1.args"]:call()
+---
+- error: invalid argument count
+...
+box.func["function1.args"]:call({ "xx" })
+---
+- error: first tuple field must be uint
+...
+box.func["function1.args"]:call({ 15 })
+---
+- [15, 'hello']
+...
box.schema.func.drop("function1.args")
---
...
@@ -260,6 +279,10 @@ s:drop()
test_run = require('test_run').new()
---
...
+test_run:cmd("push filter '(.builtin/.*.lua):[0-9]+' to '\\1'")
+---
+- true
+...
identifier = require("identifier")
---
...
@@ -299,3 +322,195 @@ test_run:cmd("setopt delimiter ''");
c:close()
---
...
+-- Test registered functions interface.
+function divide(a, b) return a / b end
+---
+...
+box.schema.func.create("divide")
+---
+...
+func = box.func.divide
+---
+...
+func.call({4, 2})
+---
+- error: 'builtin/box/schema.lua: Use func:call(...) instead of func.call(...)'
+...
+func:call(4, 2)
+---
+- error: 'builtin/box/schema.lua: Use func:call(table)'
+...
+func:call()
+---
+- error: '[string "function divide(a, b) return a / b end "]:1: attempt to perform
+ arithmetic on local ''a'' (a nil value)'
+...
+func:call({})
+---
+- error: '[string "function divide(a, b) return a / b end "]:1: attempt to perform
+ arithmetic on local ''a'' (a nil value)'
+...
+func:call({4})
+---
+- error: '[string "function divide(a, b) return a / b end "]:1: attempt to perform
+ arithmetic on local ''b'' (a nil value)'
+...
+func:call({4, 2})
+---
+- 2
+...
+func:call({4, 2, 1})
+---
+- 2
+...
+func:drop()
+---
+...
+func
+---
+- language: LUA
+ setuid: false
+ name: divide
+ id: 2
+...
+func.drop()
+---
+- error: 'builtin/box/schema.lua: Use func:drop(...) instead of func.drop(...)'
+...
+func:drop()
+---
+- error: Function 'divide' does not exist
+...
+func:call({4, 2})
+---
+- error: Function 'divide' does not exist
+...
+box.internal.func_call('divide', 4, 2)
+---
+- error: Function 'divide' does not exist
+...
+box.schema.func.create("function1.divide", {language = 'C'})
+---
+...
+func = box.func["function1.divide"]
+---
+...
+func:call(4, 2)
+---
+- error: 'builtin/box/schema.lua: Use func:call(table)'
+...
+func:call()
+---
+- error: invalid argument
+...
+func:call({})
+---
+- error: invalid argument
+...
+func:call({4})
+---
+- error: invalid argument
+...
+func:call({4, 2})
+---
+- [2]
+...
+func:call({4, 2, 1})
+---
+- [2]
+...
+func:drop()
+---
+...
+func
+---
+- language: C
+ setuid: false
+ name: function1.divide
+ id: 2
+...
+func:drop()
+---
+- error: Function 'function1.divide' does not exist
+...
+func:call({4, 2})
+---
+- error: Function 'function1.divide' does not exist
+...
+box.internal.func_call('function1.divide', 4, 2)
+---
+- error: Function 'function1.divide' does not exist
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function minmax(array)
+ local min = 999
+ local max = -1
+ for _, v in pairs(array) do
+ min = math.min(min, v)
+ max = math.max(max, v)
+ end
+ return min, max
+end
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.schema.func.create("minmax")
+---
+...
+func = box.func.minmax
+---
+...
+func:call({{1, 2, 99, 3, -1}})
+---
+- -1
+- 99
+...
+func:drop()
+---
+...
+-- Test access checks for registered functions.
+function secret() return 1 end
+---
+...
+box.schema.func.create("secret")
+---
+...
+box.func.secret:call({})
+---
+- 1
+...
+function secret_leak() return box.func.secret:call() end
+---
+...
+box.schema.func.create('secret_leak')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'secret_leak')
+---
+...
+conn = net.connect(box.cfg.listen)
+---
+...
+conn:call('secret_leak')
+---
+- error: Execute access to function 'secret' is denied for user 'guest'
+...
+conn:close()
+---
+...
+box.schema.user.revoke('guest', 'execute', 'function', 'secret_leak')
+---
+...
+box.schema.func.drop('secret_leak')
+---
+...
+box.schema.func.drop('secret')
+---
+...
+test_run:cmd("clear filter")
+---
+- true
+...
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index e983495b6..25966b915 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -21,6 +21,10 @@ box.schema.user.grant('guest', 'execute', 'function', 'function1.args')
c:call('function1.args')
c:call('function1.args', { "xx" })
c:call('function1.args', { 15 })
+box.func["function1.args"]
+box.func["function1.args"]:call()
+box.func["function1.args"]:call({ "xx" })
+box.func["function1.args"]:call({ 15 })
box.schema.func.drop("function1.args")
box.schema.func.create('function1.multi_inc', {language = "C"})
@@ -86,6 +90,7 @@ s:drop()
-- gh-2914: check identifier constraints.
test_run = require('test_run').new()
+test_run:cmd("push filter '(.builtin/.*.lua):[0-9]+' to '\\1'")
identifier = require("identifier")
test_run:cmd("setopt delimiter ';'")
--
@@ -112,3 +117,67 @@ identifier.run_test(
);
test_run:cmd("setopt delimiter ''");
c:close()
+
+-- Test registered functions interface.
+function divide(a, b) return a / b end
+box.schema.func.create("divide")
+func = box.func.divide
+func.call({4, 2})
+func:call(4, 2)
+func:call()
+func:call({})
+func:call({4})
+func:call({4, 2})
+func:call({4, 2, 1})
+func:drop()
+func
+func.drop()
+func:drop()
+func:call({4, 2})
+box.internal.func_call('divide', 4, 2)
+
+box.schema.func.create("function1.divide", {language = 'C'})
+func = box.func["function1.divide"]
+func:call(4, 2)
+func:call()
+func:call({})
+func:call({4})
+func:call({4, 2})
+func:call({4, 2, 1})
+func:drop()
+func
+func:drop()
+func:call({4, 2})
+box.internal.func_call('function1.divide', 4, 2)
+
+test_run:cmd("setopt delimiter ';'")
+function minmax(array)
+ local min = 999
+ local max = -1
+ for _, v in pairs(array) do
+ min = math.min(min, v)
+ max = math.max(max, v)
+ end
+ return min, max
+end
+test_run:cmd("setopt delimiter ''");
+box.schema.func.create("minmax")
+func = box.func.minmax
+func:call({{1, 2, 99, 3, -1}})
+func:drop()
+
+-- Test access checks for registered functions.
+function secret() return 1 end
+box.schema.func.create("secret")
+box.func.secret:call({})
+function secret_leak() return box.func.secret:call() end
+box.schema.func.create('secret_leak')
+box.schema.user.grant('guest', 'execute', 'function', 'secret_leak')
+conn = net.connect(box.cfg.listen)
+conn:call('secret_leak')
+conn:close()
+box.schema.user.revoke('guest', 'execute', 'function', 'secret_leak')
+box.schema.func.drop('secret_leak')
+box.schema.func.drop('secret')
+
+test_run:cmd("clear filter")
diff --git a/test/box/misc.result b/test/box/misc.result
index dfa06724c..ec2c4fa95 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -65,6 +65,7 @@ t
- error
- execute
- feedback
+ - func
- index
- info
- internal
--
2.21.0
More information about the Tarantool-patches
mailing list