From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Kirill Shcherbatov Subject: [PATCH v3 6/6] box: introduce Lua persistent functions Date: Thu, 13 Jun 2019 17:08:26 +0300 Message-Id: <0b5ebd5a39cfe1e2d964c1e29ee4c9f09fe1a751.1560433806.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit To: tarantool-patches@freelists.org, vdavydov.dev@gmail.com Cc: Kirill Shcherbatov List-ID: This patch changes a format of the _func system space to support persistent Lua functions. The format of the system space is [ UINT, UINT, STR, UINT, STR, STR, BOOL, , 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', , , , , , ) 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