[PATCH v3 6/6] box: introduce Lua persistent functions
Kirill Shcherbatov
kshcherbatov at tarantool.org
Thu Jun 13 17:08:26 MSK 2019
This patch changes a format of the _func system space
to support persistent Lua functions. The format of the
system space is
[<id> UINT, <owner> UINT, <name> STR, <setuid> UINT,
<language> STR, <body> STR, <is_deterministic> BOOL,
<is_sandboxed>, <opts> MAP]
Closes #4182
Needed for #1260
@TarantoolBot document
Title: Persistent Lua functions
Now Tarantool supports 'persistent' Lua functions.
Such functions are stored in snapshoot and are available after
restart.
To create a persistent Lua function, specify a function body
in box.schema.func.create call:
e.g. body = "function(a, b) return a + b end"
A Lua persistent function may be 'sandboxed'. The 'sandboxed'
function is executed in isolated environment:
a. only limited set of Lua functions and modules are available:
-assert -error -pairs -ipairs -next -pcall -xpcall -type
-print -select -string -tonumber -tostring -unpack -math -utf8;
b. global variables are forbidden
Finally, the new 'is_deterministic' flag allows to mark a
registered function as deterministic, i.e. the function that
can produce only one result for a given list of parameters.
The new box.schema.func.create interface is:
box.schema.func.create('funcname', <setuid = true|FALSE>,
<if_not_exists = true|FALSE>, <language = LUA|c>,
<body = string ('')>, <is_deterministic = true|FALSE>,
<is_sandboxed = true|FALSE>)
Example:
lua_code = [[function(a, b) return a + b end]]
box.schema.func.create('sum', {body = lua_code,
is_deterministic = true, is_sandboxed = true})
box.func.sum
---
- is_sandboxed: true
is_deterministic: true
id: 2
setuid: false
body: function(a, b) return a + b end
name: sum
language: LUA
...
box.func.sum:call({1, 3})
---
- 4
...
---
src/box/alter.cc | 53 +++++++---
src/box/bootstrap.snap | Bin 4475 -> 4532 bytes
src/box/func.c | 7 +-
src/box/func_def.c | 8 ++
src/box/func_def.h | 20 +++-
src/box/lua/call.c | 182 ++++++++++++++++++++++++++++++++++-
src/box/lua/schema.lua | 12 ++-
src/box/lua/upgrade.lua | 25 ++++-
src/box/schema_def.h | 4 +
test/box-py/bootstrap.result | 6 +-
test/box/access_misc.result | 6 +-
test/box/function1.result | 145 +++++++++++++++++++++++++---
test/box/function1.test.lua | 50 +++++++++-
13 files changed, 481 insertions(+), 37 deletions(-)
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 081786820..5232a56c1 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2537,27 +2537,45 @@ func_def_get_ids_from_tuple(struct tuple *tuple, uint32_t *fid, uint32_t *uid)
static struct func_def *
func_def_new_from_tuple(struct tuple *tuple)
{
- uint32_t len;
- const char *name = tuple_field_str_xc(tuple, BOX_FUNC_FIELD_NAME,
- &len);
- if (len > BOX_NAME_MAX)
+ uint32_t field_count = tuple_field_count(tuple);
+ uint32_t name_len, body_len;
+ const char *name, *body;
+ name = tuple_field_str_xc(tuple, BOX_FUNC_FIELD_NAME, &name_len);
+ if (name_len > BOX_NAME_MAX) {
tnt_raise(ClientError, ER_CREATE_FUNCTION,
tt_cstr(name, BOX_INVALID_NAME_MAX),
"function name is too long");
- identifier_check_xc(name, len);
- struct func_def *def = (struct func_def *) malloc(func_def_sizeof(len));
+ }
+ identifier_check_xc(name, name_len);
+ if (field_count > BOX_FUNC_FIELD_BODY) {
+ body = tuple_field_str_xc(tuple, BOX_FUNC_FIELD_BODY,
+ &body_len);
+ } else {
+ body = NULL;
+ body_len = 0;
+ }
+ uint32_t def_sz = func_def_sizeof(name_len, body_len);
+ struct func_def *def =
+ (struct func_def *) malloc(def_sz);
if (def == NULL)
- tnt_raise(OutOfMemory, func_def_sizeof(len), "malloc", "def");
+ tnt_raise(OutOfMemory, def_sz, "malloc", "def");
auto def_guard = make_scoped_guard([=] { free(def); });
func_def_get_ids_from_tuple(tuple, &def->fid, &def->uid);
- memcpy(def->name, name, len);
- def->name[len] = 0;
- def->name_len = len;
- if (tuple_field_count(tuple) > BOX_FUNC_FIELD_SETUID)
+ memcpy(def->name, name, name_len);
+ def->name[name_len] = 0;
+ def->name_len = name_len;
+ if (body_len > 0) {
+ def->body = def->name + name_len + 1;
+ memcpy(def->body, body, body_len);
+ def->body[body_len] = 0;
+ } else {
+ def->body = NULL;
+ }
+ if (field_count > BOX_FUNC_FIELD_SETUID)
def->setuid = tuple_field_u32_xc(tuple, BOX_FUNC_FIELD_SETUID);
else
def->setuid = false;
- if (tuple_field_count(tuple) > BOX_FUNC_FIELD_LANGUAGE) {
+ if (field_count > BOX_FUNC_FIELD_LANGUAGE) {
const char *language =
tuple_field_cstr_xc(tuple, BOX_FUNC_FIELD_LANGUAGE);
def->language = STR2ENUM(func_language, language);
@@ -2569,6 +2587,17 @@ func_def_new_from_tuple(struct tuple *tuple)
/* Lua is the default. */
def->language = FUNC_LANGUAGE_LUA;
}
+ if (field_count > BOX_FUNC_FIELD_BODY) {
+ assert(field_count > BOX_FUNC_FIELD_OPTS);
+ def->is_deterministic =
+ tuple_field_bool_xc(tuple,
+ BOX_FUNC_FIELD_IS_DETERMINISTIC);
+ def->is_sandboxed =
+ tuple_field_bool_xc(tuple,
+ BOX_FUNC_FIELD_IS_SANDBOXED);
+ } else {
+ def->is_deterministic = false;
+ }
def_guard.is_active = false;
return def;
}
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 56943ef7e7d0fe0e3dbb5d346544e2c78c3dc154..fb313d66eaa965afb373ef7820804365309886bd 100644
GIT binary patch
literal 4532
diff --git a/src/box/func.c b/src/box/func.c
index 20ff56668..f0ec97c27 100644
--- a/src/box/func.c
+++ b/src/box/func.c
@@ -417,8 +417,13 @@ static struct func_vtab func_c_vtab;
struct func *
func_c_new(struct func_def *def)
{
- (void) def;
assert(def->language == FUNC_LANGUAGE_C);
+ if (def->body != NULL || def->is_sandboxed) {
+ diag_set(ClientError, ER_CREATE_FUNCTION, def->name,
+ "body and is_sandboxed options are not compatible "
+ "with C language");
+ return NULL;
+ }
struct func_c *func = (struct func_c *) malloc(sizeof(struct func_c));
if (func == NULL) {
diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
diff --git a/src/box/func_def.c b/src/box/func_def.c
index 2b135e2d7..73e493786 100644
--- a/src/box/func_def.c
+++ b/src/box/func_def.c
@@ -14,7 +14,15 @@ func_def_cmp(struct func_def *def1, struct func_def *def2)
return def1->setuid - def2->setuid;
if (def1->language != def2->language)
return def1->language - def2->language;
+ if (def1->is_deterministic != def2->is_deterministic)
+ return def1->is_deterministic - def2->is_deterministic;
+ if (def1->is_sandboxed != def2->is_sandboxed)
+ return def1->is_sandboxed - def2->is_sandboxed;
if (strcmp(def1->name, def2->name) != 0)
return strcmp(def1->name, def2->name);
+ if ((def1->body != NULL) != (def2->body != NULL))
+ return def1->body - def2->body;
+ if (def1->body != NULL && strcmp(def1->body, def2->body) != 0)
+ return strcmp(def1->body, def2->body);
return 0;
}
diff --git a/src/box/func_def.h b/src/box/func_def.h
index 7df6678d8..4886d5440 100644
--- a/src/box/func_def.h
+++ b/src/box/func_def.h
@@ -58,11 +58,24 @@ struct func_def {
uint32_t fid;
/** Owner of the function. */
uint32_t uid;
+ /** Definition of the persistent function. */
+ char *body;
/**
* True if the function requires change of user id before
* invocation.
*/
bool setuid;
+ /**
+ * Whether this function is deterministic (can produce
+ * only one result for a given list of parameters).
+ */
+ bool is_deterministic;
+ /**
+ * Whether the routine mast be initialized with isolated
+ * sandbox where only a limited number if functions is
+ * available.
+ */
+ bool is_sandboxed;
/**
* The language of the stored function.
*/
@@ -79,10 +92,13 @@ struct func_def {
* for a function of length @a a name_len.
*/
static inline size_t
-func_def_sizeof(uint32_t name_len)
+func_def_sizeof(uint32_t name_len, uint32_t body_len)
{
/* +1 for '\0' name terminating. */
- return sizeof(struct func_def) + name_len + 1;
+ size_t sz = sizeof(struct func_def) + name_len + 1;
+ if (body_len > 0)
+ sz += body_len + 1;
+ return sz;
}
/** Compare two given function definitions. */
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 90c28a160..43445373f 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -573,22 +573,142 @@ box_lua_eval(const char *expr, uint32_t expr_len,
struct func_lua {
/** Function object base class. */
struct func base;
+ /**
+ * The reference index of Lua function object.
+ * Is equal to LUA_REFNIL when undefined.
+ */
+ int lua_ref;
};
static struct func_vtab func_lua_vtab;
+static struct func_vtab func_persistent_lua_vtab;
+
+static const char *default_sandbox_exports[] = {
+ "assert", "error", "ipairs", "math", "next", "pairs", "pcall", "print",
+ "select", "string", "table", "tonumber", "tostring", "type", "unpack",
+ "xpcall", "utf8",
+};
+
+/**
+ * Assemble a new sandbox with given exports table on top of the
+ * Lua stack. All modules in exports list are copying deeply
+ * to ensure the immutablility of this system object.
+ */
+static int
+luaT_prepare_sandbox(struct lua_State *L, const char *exports[],
+ uint32_t exports_count)
+{
+ int count, rc = -1;
+ const char *deepcopy = "table.deepcopy";
+ int luaL_deepcopy_func_ref = LUA_REFNIL;
+ if (luaT_func_find(L, deepcopy, deepcopy + strlen(deepcopy),
+ &count) != 0)
+ goto end;
+ luaL_deepcopy_func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+ assert(luaL_deepcopy_func_ref != LUA_REFNIL);
+ lua_createtable(L, exports_count, 0);
+ for (unsigned i = 0; i < exports_count; i++) {
+ int count;
+ uint32_t name_len = strlen(exports[i]);
+ if (luaT_func_find(L, exports[i], exports[i] + name_len,
+ &count) != 0)
+ goto end;
+ switch (lua_type(L, -1)) {
+ case LUA_TTABLE:
+ lua_rawgeti(L, LUA_REGISTRYINDEX,
+ luaL_deepcopy_func_ref);
+ lua_insert(L, -2);
+ lua_call(L, 1, LUA_MULTRET);
+ FALLTHROUGH;
+ case LUA_TFUNCTION:
+ break;
+ default:
+ unreachable();
+ }
+ lua_setfield(L, -2, exports[i]);
+ }
+ rc = 0;
+end:
+ luaL_unref(tarantool_L, LUA_REGISTRYINDEX, luaL_deepcopy_func_ref);
+ return rc;
+}
+
+static int
+func_persistent_lua_load(struct lua_State *L)
+{
+ struct func_lua *func = (struct func_lua *) lua_topointer(L, 1);
+ assert(func->base.def->body != NULL);
+ lua_settop(L, 0);
+
+ struct region *region = &fiber()->gc;
+ size_t region_svp = region_used(region);
+ const char *load_pref = "return ";
+ uint32_t load_str_sz =
+ strlen(load_pref) + strlen(func->base.def->body) + 1;
+ char *load_str = region_alloc(region, load_str_sz);
+ if (load_str == NULL) {
+ diag_set(OutOfMemory, load_str_sz, "region", "load_str");
+ goto error;
+ }
+ sprintf(load_str, "%s%s", load_pref, func->base.def->body);
+ if (luaL_loadstring(L, load_str) != 0 ||
+ luaT_call(L, 0, 1) != 0 || !lua_isfunction(L, -1)) {
+ diag_set(ClientError, ER_LOAD_FUNCTION, func->base.def->name,
+ func->base.def->body);
+ goto error;
+ }
+ if (func->base.def->is_sandboxed) {
+ if (luaT_prepare_sandbox(L, default_sandbox_exports,
+ nelem(default_sandbox_exports)) != 0) {
+ diag_set(ClientError, ER_LOAD_FUNCTION,
+ func->base.def->name, func->base.def->body);
+ goto error;
+ }
+ lua_setfenv(L, -2);
+ }
+ func->lua_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+ region_truncate(region, region_svp);
+ return lua_gettop(L);
+error:
+ region_truncate(region, region_svp);
+ return luaT_error(L);
+}
struct func *
func_lua_new(struct func_def *def)
{
- (void) def;
assert(def->language == FUNC_LANGUAGE_LUA);
+ if (def->is_sandboxed && def->body == NULL) {
+ diag_set(ClientError, ER_CREATE_FUNCTION, def->name,
+ "is_sandboxed option may be set only for persistent "
+ "Lua function (when body option is set)");
+ return NULL;
+ }
struct func_lua *func =
(struct func_lua *) malloc(sizeof(struct func_lua));
if (func == NULL) {
diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
return NULL;
}
- func->base.vtab = &func_lua_vtab;
+ if (def->body != NULL) {
+ /** Load persistent Lua function. */
+ struct lua_State *L = lua_newthread(tarantool_L);
+ int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
+ lua_pushcfunction(L, func_persistent_lua_load);
+ func->base.def = def;
+ lua_pushlightuserdata(L, func);
+ int rc = luaT_call(L, 1, LUA_MULTRET);
+ luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
+ if (rc != 0) {
+ free(func);
+ return NULL;
+ }
+ assert(func->lua_ref != LUA_REFNIL);
+ func->base.vtab = &func_persistent_lua_vtab;
+ } else {
+ func->lua_ref = LUA_REFNIL;
+ func->base.vtab = &func_lua_vtab;
+ }
return &func->base;
}
@@ -614,6 +734,53 @@ static struct func_vtab func_lua_vtab = {
.destroy = func_lua_unload,
};
+static void
+func_persistent_lua_unload(struct func *base)
+{
+ assert(base != NULL && base->def->language == FUNC_LANGUAGE_LUA &&
+ base->def->body != NULL);
+ assert(base->vtab == &func_persistent_lua_vtab);
+ struct func_lua *func = (struct func_lua *) base;
+ luaL_unref(tarantool_L, LUA_REGISTRYINDEX, func->lua_ref);
+ func->lua_ref = LUA_REFNIL;
+}
+
+static int
+func_persistent_lua_call_cb(lua_State *L)
+{
+ struct port *in_port = (struct port *) lua_topointer(L, 1);
+ struct func_lua *func = (struct func_lua *) lua_topointer(L, 2);
+ lua_settop(L, 0); /* clear the stack to simplify the logic below */
+
+ assert(func->lua_ref != LUA_REFNIL);
+ lua_rawgeti(L, LUA_REGISTRYINDEX, func->lua_ref);
+
+ /* Push the rest of args (a tuple). */
+ int top = lua_gettop(L);
+ port_dump_lua(in_port, L, true);
+ int arg_count = lua_gettop(L) - top;
+
+ lua_call(L, arg_count, LUA_MULTRET);
+ return lua_gettop(L);
+}
+
+static inline int
+func_persistent_lua_call(struct func *base, struct port *in_port,
+ struct port *out_port)
+{
+ assert(base != NULL && base->def->language == FUNC_LANGUAGE_LUA &&
+ base->def->body != NULL);
+ assert(base->vtab == &func_persistent_lua_vtab);
+ return box_process_lua(func_persistent_lua_call_cb, base,
+ in_port, out_port);
+
+}
+
+static struct func_vtab func_persistent_lua_vtab = {
+ .call = func_persistent_lua_call,
+ .destroy = func_persistent_lua_unload,
+};
+
static int
lbox_module_reload(lua_State *L)
{
@@ -707,6 +874,17 @@ lbox_func_new(struct lua_State *L, struct func *func)
lua_pushstring(L, "language");
lua_pushstring(L, func_language_strs[func->def->language]);
lua_settable(L, top);
+ lua_pushstring(L, "is_deterministic");
+ lua_pushboolean(L, func->def->is_deterministic);
+ lua_settable(L, top);
+ if (func->def->body != NULL) {
+ lua_pushstring(L, "body");
+ lua_pushstring(L, func->def->body);
+ lua_settable(L, top);
+ lua_pushstring(L, "is_sandboxed");
+ lua_pushboolean(L, func->def->is_sandboxed);
+ lua_settable(L, top);
+ }
/* Bless func object. */
lua_getfield(L, LUA_GLOBALSINDEX, "box");
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 9c3ee063c..9d8df54dc 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -2138,7 +2138,9 @@ box.schema.func.create = function(name, opts)
opts = opts or {}
check_param_table(opts, { setuid = 'boolean',
if_not_exists = 'boolean',
- language = 'string'})
+ language = 'string', body = 'string',
+ is_deterministic = 'boolean',
+ is_sandboxed = 'boolean', opts = 'table'})
local _func = box.space[box.schema.FUNC_ID]
local _vfunc = box.space[box.schema.VFUNC_ID]
local func = _vfunc.index.name:get{name}
@@ -2148,10 +2150,14 @@ box.schema.func.create = function(name, opts)
end
return
end
- opts = update_param_table(opts, { setuid = false, language = 'lua'})
+ opts = update_param_table(opts, { setuid = false, language = 'lua',
+ body = '', is_deterministic = false,
+ is_sandboxed = false, opts = setmap{}})
opts.language = string.upper(opts.language)
opts.setuid = opts.setuid and 1 or 0
- _func:auto_increment{session.euid(), name, opts.setuid, opts.language}
+ _func:auto_increment{session.euid(), name, opts.setuid, opts.language,
+ opts.body, opts.is_deterministic, opts.is_sandboxed,
+ opts.opts}
end
box.schema.func.drop = function(name, opts)
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index 3385b8e17..f2edf86df 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -326,7 +326,8 @@ local function initial_1_7_5()
-- create "box.schema.user.info" function
log.info('create function "box.schema.user.info" with setuid')
- _func:replace{1, ADMIN, 'box.schema.user.info', 1, 'LUA'}
+ _func:replace{1, ADMIN, 'box.schema.user.info', 1, 'LUA',
+ '', false, false, MAP}
-- grant 'public' role access to 'box.schema.user.info' function
log.info('grant execute on function "box.schema.user.info" to public')
@@ -820,10 +821,32 @@ local function create_vcollation_space()
box.space[box.schema.VCOLLATION_ID]:format(format)
end
+local function upgrade_func_to_2_2_1()
+ log.info("Update _func format")
+ local _func = box.space[box.schema.FUNC_ID]
+ local format = {}
+ format[1] = {name='id', type='unsigned'}
+ format[2] = {name='owner', type='unsigned'}
+ format[3] = {name='name', type='string'}
+ format[4] = {name='setuid', type='unsigned'}
+ format[5] = {name='language', type='string'}
+ format[6] = {name='body', type='string'}
+ format[7] = {name='is_deterministic', type='boolean'}
+ format[8] = {name='is_sandboxed', type='boolean'}
+ format[9] = {name='opts', type='map'}
+ for _, v in box.space._func:pairs() do
+ _ = box.space._func:replace({v.id, v.owner, v.name, v.setuid,
+ v[5] or 'LUA', '', false, false,
+ setmap({})})
+ end
+ _func:format(format)
+end
+
local function upgrade_to_2_2_1()
upgrade_sequence_to_2_2_1()
upgrade_ck_constraint_to_2_2_1()
create_vcollation_space()
+ upgrade_func_to_2_2_1()
end
--------------------------------------------------------------------------------
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index 88b5502b8..ac2b3bfef 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -167,6 +167,10 @@ enum {
BOX_FUNC_FIELD_NAME = 2,
BOX_FUNC_FIELD_SETUID = 3,
BOX_FUNC_FIELD_LANGUAGE = 4,
+ BOX_FUNC_FIELD_BODY = 5,
+ BOX_FUNC_FIELD_IS_DETERMINISTIC = 6,
+ BOX_FUNC_FIELD_IS_SANDBOXED = 7,
+ BOX_FUNC_FIELD_OPTS = 8,
};
/** _collation fields. */
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index b20dc41e5..5ca7f3740 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -53,7 +53,9 @@ box.space._space:select{}
'type': 'string'}, {'name': 'opts', 'type': 'map'}, {'name': 'parts', 'type': 'array'}]]
- [296, 1, '_func', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'setuid',
- 'type': 'unsigned'}]]
+ 'type': 'unsigned'}, {'name': 'language', 'type': 'string'}, {'name': 'body',
+ 'type': 'string'}, {'name': 'is_deterministic', 'type': 'boolean'}, {'name': 'is_sandboxed',
+ 'type': 'boolean'}, {'name': 'opts', 'type': 'map'}]]
- [297, 1, '_vfunc', 'sysview', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'setuid',
'type': 'unsigned'}]]
@@ -152,7 +154,7 @@ box.space._user:select{}
...
box.space._func:select{}
---
-- - [1, 1, 'box.schema.user.info', 1, 'LUA']
+- - [1, 1, 'box.schema.user.info', 1, 'LUA', '', false, false, {}]
...
box.space._priv:select{}
---
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 53d366106..e7a6f0984 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -793,7 +793,9 @@ box.space._space:select()
'type': 'string'}, {'name': 'opts', 'type': 'map'}, {'name': 'parts', 'type': 'array'}]]
- [296, 1, '_func', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'setuid',
- 'type': 'unsigned'}]]
+ 'type': 'unsigned'}, {'name': 'language', 'type': 'string'}, {'name': 'body',
+ 'type': 'string'}, {'name': 'is_deterministic', 'type': 'boolean'}, {'name': 'is_sandboxed',
+ 'type': 'boolean'}, {'name': 'opts', 'type': 'map'}]]
- [297, 1, '_vfunc', 'sysview', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'setuid',
'type': 'unsigned'}]]
@@ -829,7 +831,7 @@ box.space._space:select()
...
box.space._func:select()
---
-- - [1, 1, 'box.schema.user.info', 1, 'LUA']
+- - [1, 1, 'box.schema.user.info', 1, 'LUA', '', false, false, {}]
...
session = nil
---
diff --git a/test/box/function1.result b/test/box/function1.result
index 331bd466a..172f56082 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -16,7 +16,10 @@ c = net.connect(os.getenv("LISTEN"))
box.schema.func.create('function1', {language = "C"})
---
...
-box.space._func:replace{2, 1, 'function1', 0, 'LUA'}
+function setmap(tab) return setmetatable(tab, { __serialize = 'map' }) end
+---
+...
+box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', false, false, setmap({})}
---
- error: function does not support alter
...
@@ -59,10 +62,11 @@ c:call('function1.args', { 15 })
...
box.func["function1.args"]
---
-- language: C
+- is_deterministic: false
+ id: 2
setuid: false
name: function1.args
- id: 2
+ language: C
...
box.func["function1.args"]:call()
---
@@ -330,11 +334,11 @@ func = box.func.divide
...
func.call({4, 2})
---
-- error: 'builtin/box/schema.lua:2197: Use func:call(...) instead of func.call(...)'
+- error: 'builtin/box/schema.lua:2203: Use func:call(...) instead of func.call(...)'
...
func:call(4, 2)
---
-- error: 'builtin/box/schema.lua:2212: Use func:call(table)'
+- error: 'builtin/box/schema.lua:2218: Use func:call(table)'
...
func:call()
---
@@ -362,14 +366,15 @@ func:drop()
...
func
---
-- language: LUA
+- is_deterministic: false
+ id: 2
setuid: false
name: divide
- id: 2
+ language: LUA
...
func.drop()
---
-- error: 'builtin/box/schema.lua:2197: Use func:drop(...) instead of func.drop(...)'
+- error: 'builtin/box/schema.lua:2203: Use func:drop(...) instead of func.drop(...)'
...
func:drop()
---
@@ -391,7 +396,7 @@ func = box.func["function1.divide"]
...
func:call(4, 2)
---
-- error: 'builtin/box/schema.lua:2212: Use func:call(table)'
+- error: 'builtin/box/schema.lua:2218: Use func:call(table)'
...
func:call()
---
@@ -418,10 +423,11 @@ func:drop()
...
func
---
-- language: C
+- is_deterministic: false
+ id: 2
setuid: false
name: function1.divide
- id: 2
+ language: C
...
func:drop()
---
@@ -504,3 +510,120 @@ box.schema.func.drop('secret_leak')
box.schema.func.drop('secret')
---
...
+--
+-- gh-4182: Introduce persistent Lua functions.
+--
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+body = [[function(tuple)
+ if type(tuple.address) ~= 'string' then
+ return nil, 'Invalid field type'
+ end
+ local t = tuple.address:upper():split()
+ for k,v in pairs(t) do t[k] = v end
+ return t
+ end
+]]
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.schema.func.create('addrsplit', {body = body, language = "C"})
+---
+- error: 'Failed to create function ''addrsplit'': body and is_sandboxed options are
+ not compatible with C language'
+...
+box.schema.func.create('addrsplit', {is_sandboxed = true, language = "C"})
+---
+- error: 'Failed to create function ''addrsplit'': body and is_sandboxed options are
+ not compatible with C language'
+...
+box.schema.func.create('addrsplit', {is_sandboxed = true})
+---
+- error: 'Failed to create function ''addrsplit'': is_sandboxed option may be set
+ only for persistent Lua function (when body option is set)'
+...
+box.schema.func.create('invalid', {body = "function(tuple) ret tuple"})
+---
+- error: 'Failed to dynamically load function ''invalid'': function(tuple) ret tuple'
+...
+box.schema.func.create('addrsplit', {body = body, is_deterministic = true})
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'addrsplit')
+---
+...
+conn = net.connect(box.cfg.listen)
+---
+...
+conn:call('addrsplit', {{address = "Moscow Dolgoprudny"}})
+---
+- ['MOSCOW', 'DOLGOPRUDNY']
+...
+box.func.addrsplit:call({{address = "Moscow Dolgoprudny"}})
+---
+- - MOSCOW
+ - DOLGOPRUDNY
+...
+conn:close()
+---
+...
+box.snapshot()
+---
+- ok
+...
+test_run:cmd("restart server default")
+test_run = require('test_run').new()
+---
+...
+net = require('net.box')
+---
+...
+conn = net.connect(box.cfg.listen)
+---
+...
+conn:call('addrsplit', {{address = "Moscow Dolgoprudny"}})
+---
+- ['MOSCOW', 'DOLGOPRUDNY']
+...
+box.func.addrsplit:call({{address = "Moscow Dolgoprudny"}})
+---
+- - MOSCOW
+ - DOLGOPRUDNY
+...
+conn:close()
+---
+...
+box.schema.user.revoke('guest', 'execute', 'function', 'addrsplit')
+---
+...
+box.func.addrsplit:drop()
+---
+...
+-- Test snadboxed functions.
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+body = [[function(number)
+ math.abs = math.log
+ return math.abs(number)
+ end]]
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.schema.func.create('monkey', {body = body, is_sandboxed = true})
+---
+...
+box.func.monkey:call({1})
+---
+- 0
+...
+math.abs(1)
+---
+- 1
+...
+box.func.monkey:drop()
+---
+...
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index cf4a71979..b4021a704 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -7,7 +7,8 @@ net = require('net.box')
c = net.connect(os.getenv("LISTEN"))
box.schema.func.create('function1', {language = "C"})
-box.space._func:replace{2, 1, 'function1', 0, 'LUA'}
+function setmap(tab) return setmetatable(tab, { __serialize = 'map' }) end
+box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', false, false, setmap({})}
box.schema.user.grant('guest', 'execute', 'function', 'function1')
_ = box.schema.space.create('test')
_ = box.space.test:create_index('primary')
@@ -178,3 +179,50 @@ conn:close()
box.schema.user.revoke('guest', 'execute', 'function', 'secret_leak')
box.schema.func.drop('secret_leak')
box.schema.func.drop('secret')
+
+--
+-- gh-4182: Introduce persistent Lua functions.
+--
+test_run:cmd("setopt delimiter ';'")
+body = [[function(tuple)
+ if type(tuple.address) ~= 'string' then
+ return nil, 'Invalid field type'
+ end
+ local t = tuple.address:upper():split()
+ for k,v in pairs(t) do t[k] = v end
+ return t
+ end
+]]
+test_run:cmd("setopt delimiter ''");
+box.schema.func.create('addrsplit', {body = body, language = "C"})
+box.schema.func.create('addrsplit', {is_sandboxed = true, language = "C"})
+box.schema.func.create('addrsplit', {is_sandboxed = true})
+box.schema.func.create('invalid', {body = "function(tuple) ret tuple"})
+box.schema.func.create('addrsplit', {body = body, is_deterministic = true})
+box.schema.user.grant('guest', 'execute', 'function', 'addrsplit')
+conn = net.connect(box.cfg.listen)
+conn:call('addrsplit', {{address = "Moscow Dolgoprudny"}})
+box.func.addrsplit:call({{address = "Moscow Dolgoprudny"}})
+conn:close()
+box.snapshot()
+test_run:cmd("restart server default")
+test_run = require('test_run').new()
+net = require('net.box')
+conn = net.connect(box.cfg.listen)
+conn:call('addrsplit', {{address = "Moscow Dolgoprudny"}})
+box.func.addrsplit:call({{address = "Moscow Dolgoprudny"}})
+conn:close()
+box.schema.user.revoke('guest', 'execute', 'function', 'addrsplit')
+box.func.addrsplit:drop()
+
+-- Test snadboxed functions.
+test_run:cmd("setopt delimiter ';'")
+body = [[function(number)
+ math.abs = math.log
+ return math.abs(number)
+ end]]
+test_run:cmd("setopt delimiter ''");
+box.schema.func.create('monkey', {body = body, is_sandboxed = true})
+box.func.monkey:call({1})
+math.abs(1)
+box.func.monkey:drop()
--
2.21.0
More information about the Tarantool-patches
mailing list