diff --git a/src/box/alter.cc b/src/box/alter.cc
index ff6f5c4b843befe7ba30a740986aa77eba509b96..33f9b0a719ba78c5890411a7945c4bf7fcf52d2b 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2537,83 +2537,31 @@ 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 field_count = tuple_field_count(tuple);
-	uint32_t name_len, body_len, comment_len;
-	const char *name, *body, *comment;
-	name = tuple_field_str_xc(tuple, BOX_FUNC_FIELD_NAME, &name_len);
-	if (name_len > BOX_NAME_MAX) {
+	uint32_t len;
+	const char *name = tuple_field_str_xc(tuple, BOX_FUNC_FIELD_NAME,
+					      &len);
+	if (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, name_len);
-	if (field_count > BOX_FUNC_FIELD_BODY) {
-		body = tuple_field_str_xc(tuple, BOX_FUNC_FIELD_BODY,
-					  &body_len);
-		comment = tuple_field_str_xc(tuple, BOX_FUNC_FIELD_COMMENT,
-					     &comment_len);
-		uint32_t len;
-		const char *routine_type = tuple_field_str_xc(tuple,
-					BOX_FUNC_FIELD_ROUTINE_TYPE, &len);
-		if (len != strlen("function") ||
-		    strncasecmp(routine_type, "function", len) != 0) {
-			tnt_raise(ClientError, ER_CREATE_FUNCTION, name,
-				  "unsupported routine_type value");
-		}
-		const char *sql_data_access = tuple_field_str_xc(tuple,
-					BOX_FUNC_FIELD_SQL_DATA_ACCESS, &len);
-		if (len != strlen("none") ||
-		    strncasecmp(sql_data_access, "none", len) != 0) {
-			tnt_raise(ClientError, ER_CREATE_FUNCTION, name,
-				  "unsupported sql_data_access value");
-		}
-		bool is_null_call = tuple_field_bool_xc(tuple,
-						BOX_FUNC_FIELD_IS_NULL_CALL);
-		if (is_null_call != true) {
-			tnt_raise(ClientError, ER_CREATE_FUNCTION, name,
-				  "unsupported is_null_call value");
-		}
-	} else {
-		body = NULL;
-		body_len = 0;
-		comment = NULL;
-		comment_len = 0;
-	}
-	uint32_t body_offset, comment_offset;
-	uint32_t def_sz = func_def_sizeof(name_len, body_len, comment_len,
-					  &body_offset, &comment_offset);
-	struct func_def *def =
-		(struct func_def *) malloc(def_sz);
+	identifier_check_xc(name, len);
+	struct func_def *def = (struct func_def *) malloc(func_def_sizeof(len));
 	if (def == NULL)
-		tnt_raise(OutOfMemory, def_sz, "malloc", "def");
+		tnt_raise(OutOfMemory, func_def_sizeof(len), "malloc", "def");
 	auto def_guard = make_scoped_guard([=] { free(def); });
 	func_def_get_ids_from_tuple(tuple, &def->fid, &def->uid);
 	if (def->fid > BOX_FUNCTION_MAX) {
 		tnt_raise(ClientError, ER_CREATE_FUNCTION,
-			  tt_cstr(name, name_len), "function id is too big");
+			  tt_cstr(name, len), "function id is too big");
 	}
-	memcpy(def->name, name, name_len);
-	def->name[name_len] = 0;
-	def->name_len = name_len;
-	if (body_len > 0) {
-		def->body = (char *)def + body_offset;
-		memcpy(def->body, body, body_len);
-		def->body[body_len] = 0;
-	} else {
-		def->body = NULL;
-	}
-	if (comment_len > 0) {
-		def->comment = (char *)def + comment_offset;
-		memcpy(def->comment, comment, comment_len);
-		def->comment[comment_len] = 0;
-	} else {
-		def->comment = NULL;
-	}
-	if (field_count > BOX_FUNC_FIELD_SETUID)
+	memcpy(def->name, name, len);
+	def->name[len] = 0;
+	def->name_len = len;
+	if (tuple_field_count(tuple) > BOX_FUNC_FIELD_SETUID)
 		def->setuid = tuple_field_u32_xc(tuple, BOX_FUNC_FIELD_SETUID);
 	else
 		def->setuid = false;
-	if (field_count > BOX_FUNC_FIELD_LANGUAGE) {
+	if (tuple_field_count(tuple) > BOX_FUNC_FIELD_LANGUAGE) {
 		const char *language =
 			tuple_field_cstr_xc(tuple, BOX_FUNC_FIELD_LANGUAGE);
 		def->language = STR2ENUM(func_language, language);
@@ -2625,17 +2573,6 @@ func_def_new_from_tuple(struct tuple *tuple)
 		/* Lua is the default. */
 		def->language = FUNC_LANGUAGE_LUA;
 	}
-	if (field_count > BOX_FUNC_FIELD_BODY) {
-		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->is_sandboxed = false;
-	}
 	def_guard.is_active = false;
 	return def;
 }
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 33d110c700b9e67d07af54a3164728733ba2100d..56943ef7e7d0fe0e3dbb5d346544e2c78c3dc154 100644
Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ
diff --git a/src/box/func.c b/src/box/func.c
index 88cf8cdc9638954001c41f7d753531c3542ab9e3..8227527ec6bc294529108aa672cdbd7eca6e17e6 100644
--- a/src/box/func.c
+++ b/src/box/func.c
@@ -416,13 +416,8 @@ static struct func_vtab func_c_vtab;
 static 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 17eafb559e01e2453fa9bc463484000acb03f965..2b135e2d770efabe5f919d0abe28138d9cbe68ed 100644
--- a/src/box/func_def.c
+++ b/src/box/func_def.c
@@ -14,19 +14,7 @@ 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);
-	if ((def1->comment != NULL) != (def2->comment != NULL))
-		return def1->comment - def2->comment;
-	if (def1->comment != NULL && strcmp(def1->comment, def2->comment) != 0)
-		return strcmp(def1->comment, def2->comment);
 	return 0;
 }
diff --git a/src/box/func_def.h b/src/box/func_def.h
index 170aef53690054b79ba6763ab084227fd09a6fe9..866d425a1cc24ba5ef5800048d660532b14ecb31 100644
--- a/src/box/func_def.h
+++ b/src/box/func_def.h
@@ -58,26 +58,11 @@ struct func_def {
 	uint32_t fid;
 	/** Owner of the function. */
 	uint32_t uid;
-	/** Definition of the persistent function. */
-	char *body;
-	/** User-defined comment for a function. */
-	char *comment;
 	/**
 	 * 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 must be initialized with isolated
-	 * sandbox where only a limited number if functions is
-	 * available.
-	 */
-	bool is_sandboxed;
 	/**
 	 * The language of the stored function.
 	 */
@@ -91,22 +76,13 @@ struct func_def {
 /**
  * @param name_len length of func_def->name
  * @returns size in bytes needed to allocate for struct func_def
- * for a function of length @a a name_len, body @a body_len and
- * with comment @a comment_len.
+ * for a function of length @a a name_len.
  */
 static inline size_t
-func_def_sizeof(uint32_t name_len, uint32_t body_len, uint32_t comment_len,
-		uint32_t *body_offset, uint32_t *comment_offset)
+func_def_sizeof(uint32_t name_len)
 {
 	/* +1 for '\0' name terminating. */
-	size_t sz = sizeof(struct func_def) + name_len + 1;
-	*body_offset = sz;
-	if (body_len > 0)
-		sz += body_len + 1;
-	*comment_offset = sz;
-	if (comment_len > 0)
-		sz += comment_len + 1;
-	return sz;
+	return sizeof(struct func_def) + name_len + 1;
 }
 
 /** Compare two given function definitions. */
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 88110c4356c7d7d80ef2118b9bdf54001d5c348d..a236325b6753a16d1722644ce980b9a68f9a284d 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -294,7 +294,6 @@ port_lua_create(struct port *port, struct lua_State *L)
 }
 
 struct execute_lua_ctx {
-	int lua_ref;
 	const char *name;
 	uint32_t name_len;
 	struct port *args;
@@ -324,24 +323,6 @@ execute_lua_call(lua_State *L)
 	return lua_gettop(L);
 }
 
-static int
-execute_lua_call_by_ref(lua_State *L)
-{
-	struct execute_lua_ctx *ctx =
-		(struct execute_lua_ctx *) lua_topointer(L, 1);
-	lua_settop(L, 0); /* clear the stack to simplify the logic below */
-
-	lua_rawgeti(L, LUA_REGISTRYINDEX, ctx->lua_ref);
-
-	/* Push the rest of args (a tuple). */
-	int top = lua_gettop(L);
-	port_dump_lua(ctx->args, L, true);
-	int arg_count = lua_gettop(L) - top;
-
-	lua_call(L, arg_count, LUA_MULTRET);
-	return lua_gettop(L);
-}
-
 static int
 execute_lua_eval(lua_State *L)
 {
@@ -553,168 +534,22 @@ box_lua_eval(const char *expr, uint32_t expr_len,
 struct func_lua {
 	/** Function object base class. */
 	struct func base;
-	/**
-	 * For a persistent function: a reference to the
-	 * function body. Otherwise LUA_REFNIL.
-	 */
-	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 the top of
- * a given Lua stack. All modules in exports list are copied
- * deeply to ensure the immutability of this system object.
- */
-static int
-prepare_lua_sandbox(struct lua_State *L, const char *exports[],
-		    int export_count)
-{
-	lua_createtable(L, export_count, 0);
-	if (export_count == 0)
-		return 0;
-	int rc = -1;
-	const char *deepcopy = "table.deepcopy";
-	int luaL_deepcopy_func_ref = LUA_REFNIL;
-	int ret = box_lua_find(L, deepcopy, deepcopy + strlen(deepcopy));
-	if (ret < 0)
-		goto end;
-	luaL_deepcopy_func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
-	assert(luaL_deepcopy_func_ref != LUA_REFNIL);
-	for (int i = 0; i < export_count; i++) {
-		uint32_t name_len = strlen(exports[i]);
-		ret = box_lua_find(L, exports[i], exports[i] + name_len);
-		if (ret < 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, 1);
-			break;
-		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;
-}
-
-/**
- * Assemble a Lua function object by user-defined function body.
- */
-static int
-func_persistent_lua_load(struct func_lua *func)
-{
-	int rc = -1;
-	int top = lua_gettop(tarantool_L);
-	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");
-		return -1;
-	}
-	sprintf(load_str, "%s%s", load_pref, func->base.def->body);
-
-	/*
-	 * Perform loading of the persistent Lua function
-	 * in a new sandboxed Lua thread. The sandbox is
-	 * required to guarantee the safety of executing
-	 * an arbitrary user-defined code
-	 * (e.g. body = 'fiber.yield()').
-	 */
-	struct lua_State *coro_L = lua_newthread(tarantool_L);
-	if (!func->base.def->is_sandboxed) {
-		/*
-		 * Keep an original env to apply for non-sandboxed
-		 * persistent function. It is required because
-		 * built object inherits parent env.
-		 */
-		lua_getfenv(tarantool_L, -1);
-		lua_insert(tarantool_L, -2);
-	}
-	if (prepare_lua_sandbox(tarantool_L, NULL, 0) != 0)
-		unreachable();
-	lua_setfenv(tarantool_L, -2);
-	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
-	if (luaL_loadstring(coro_L, load_str) != 0 ||
-	    lua_pcall(coro_L, 0, 1, 0) != 0) {
-		diag_set(ClientError, ER_LOAD_FUNCTION, func->base.def->name,
-			 luaT_tolstring(coro_L, -1, NULL));
-		goto end;
-	}
-	if (!lua_isfunction(coro_L, -1)) {
-		diag_set(ClientError, ER_LOAD_FUNCTION, func->base.def->name,
-			 "given body doesn't define a function");
-		goto end;
-	}
-	lua_xmove(coro_L, tarantool_L, 1);
-	if (func->base.def->is_sandboxed) {
-		if (prepare_lua_sandbox(tarantool_L, default_sandbox_exports,
-					nelem(default_sandbox_exports)) != 0) {
-			diag_set(ClientError, ER_LOAD_FUNCTION,
-				func->base.def->name,
-				diag_last_error(diag_get())->errmsg);
-			goto end;
-		}
-	} else {
-		lua_insert(tarantool_L, -2);
-	}
-	lua_setfenv(tarantool_L, -2);
-	func->lua_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
-	rc = 0;
-end:
-	lua_settop(tarantool_L, top);
-	region_truncate(region, region_svp);
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
-	return rc;
-}
 
 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;
 	}
-	if (def->body != NULL) {
-		func->base.def = def;
-		func->base.vtab = &func_persistent_lua_vtab;
-		if (func_persistent_lua_load(func) != 0) {
-			free(func);
-			return NULL;
-		}
-	} else {
-		func->lua_ref = LUA_REFNIL;
-		func->base.vtab = &func_lua_vtab;
-	}
+	func->base.vtab = &func_lua_vtab;
 	return &func->base;
 }
 
@@ -739,42 +574,6 @@ static struct func_vtab func_lua_vtab = {
 	.destroy = func_lua_destroy,
 };
 
-static void
-func_persistent_lua_unload(struct func_lua *func)
-{
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, func->lua_ref);
-}
-
-static void
-func_persistent_lua_destroy(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;
-	func_persistent_lua_unload(func);
-	free(func);
-}
-
-static inline int
-func_persistent_lua_call(struct func *base, struct port *args, struct port *ret)
-{
-	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;
-	struct execute_lua_ctx ctx;
-	ctx.lua_ref = func->lua_ref;
-	ctx.args = args;
-	return box_process_lua(execute_lua_call_by_ref, &ctx, ret);
-
-}
-
-static struct func_vtab func_persistent_lua_vtab = {
-	.call = func_persistent_lua_call,
-	.destroy = func_persistent_lua_destroy,
-};
-
 static int
 lbox_module_reload(lua_State *L)
 {
@@ -868,27 +667,6 @@ 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, "body");
-	if (func->def->body != NULL)
-		lua_pushstring(L, func->def->body);
-	else
-		lua_pushnil(L);
-	lua_settable(L, top);
-	lua_pushstring(L, "comment");
-	if (func->def->comment != NULL)
-		lua_pushstring(L, func->def->comment);
-	else
-		lua_pushnil(L);
-	lua_settable(L, top);
-	lua_pushstring(L, "is_deterministic");
-	lua_pushboolean(L, func->def->is_deterministic);
-	lua_settable(L, top);
-	lua_pushstring(L, "is_sandboxed");
-	if (func->def->body != NULL)
-		lua_pushboolean(L, func->def->is_sandboxed);
-	else
-		lua_pushnil(L);
-	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 1ab97440c426b73fb9e4a14ad5c04684cbbfc5cb..9c3ee063cc9da6c520252c750c78f3f39e64920c 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -2138,10 +2138,7 @@ box.schema.func.create = function(name, opts)
     opts = opts or {}
     check_param_table(opts, { setuid = 'boolean',
                               if_not_exists = 'boolean',
-                              language = 'string', body = 'string',
-                              is_deterministic = 'boolean',
-                              is_sandboxed = 'boolean',
-                              comment = 'string' })
+                              language = 'string'})
     local _func = box.space[box.schema.FUNC_ID]
     local _vfunc = box.space[box.schema.VFUNC_ID]
     local func = _vfunc.index.name:get{name}
@@ -2151,19 +2148,10 @@ box.schema.func.create = function(name, opts)
         end
         return
     end
-    local datetime = os.date("%Y-%m-%d %H:%M:%S")
-    opts = update_param_table(opts, { setuid = false, language = 'lua',
-                    body = '', routine_type = 'function', data_type = setmap{},
-                    sql_data_access = 'none', is_deterministic = false,
-                    is_sandboxed = false, is_null_call = true, opts = setmap{},
-                    comment = '', created = datetime, last_altered = datetime})
+    opts = update_param_table(opts, { setuid = false, language = 'lua'})
     opts.language = string.upper(opts.language)
     opts.setuid = opts.setuid and 1 or 0
-    _func:auto_increment{session.euid(), name, opts.setuid, opts.language,
-                         opts.body, opts.routine_type, opts.data_type,
-                         opts.sql_data_access, opts.is_deterministic,
-                         opts.is_sandboxed, opts.is_null_call, opts.opts,
-                         opts.comment, opts.created, opts.last_altered}
+    _func:auto_increment{session.euid(), name, opts.setuid, opts.language}
 end
 
 box.schema.func.drop = function(name, opts)
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index 5a8875c1033c03e59be95e3f7b579c0ac539db88..3385b8e17f8f7e769d6aff769eea100f95bf9c88 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -152,7 +152,6 @@ local function initial_1_7_5()
     local _cluster = box.space[box.schema.CLUSTER_ID]
     local _truncate = box.space[box.schema.TRUNCATE_ID]
     local MAP = setmap({})
-    local datetime = os.date("%Y-%m-%d %H:%M:%S")
 
     --
     -- _schema
@@ -327,8 +326,7 @@ 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', '', 'function',
-                  MAP, 'none', false, false, true, MAP, '', datetime, datetime}
+    _func:replace{1, ADMIN, 'box.schema.user.info', 1, 'LUA'}
 
     -- grant 'public' role access to 'box.schema.user.info' function
     log.info('grant execute on function "box.schema.user.info" to public')
@@ -822,41 +820,10 @@ 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='routine_type', type='string'}
-    format[8] = {name='data_type', type='map'}
-    format[9] = {name='sql_data_access', type='string'}
-    format[10] = {name='is_deterministic', type='boolean'}
-    format[11] = {name='is_sandboxed', type='boolean'}
-    format[12] = {name='is_null_call', type='boolean'}
-    format[13] = {name='opts', type='map'}
-    format[14] = {name='comment', type='string'}
-    format[15] = {name='created', type='string'}
-    format[16] = {name='last_altered', type='string'}
-    local datetime = os.date("%Y-%m-%d %H:%M:%S")
-    for _, v in box.space._func:pairs() do
-        _ = box.space._func:replace({v.id, v.owner, v.name, v.setuid,
-                                     v[5] or 'LUA', '', 'function', setmap({}),
-                                     'none', false, false, true, setmap({}),
-                                     '', datetime, datetime})
-    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 8543a3a77c3f92d8228acc6c9841b7597e976e95..88b5502b8c93ed54428a7f0df956fa0e4c4b3082 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -167,17 +167,6 @@ enum {
 	BOX_FUNC_FIELD_NAME = 2,
 	BOX_FUNC_FIELD_SETUID = 3,
 	BOX_FUNC_FIELD_LANGUAGE = 4,
-	BOX_FUNC_FIELD_BODY = 5,
-	BOX_FUNC_FIELD_ROUTINE_TYPE = 6,
-	BOX_FUNC_FIELD_DATA_TYPE = 7,
-	BOX_FUNC_FIELD_SQL_DATA_ACCESS = 8,
-	BOX_FUNC_FIELD_IS_DETERMINISTIC = 9,
-	BOX_FUNC_FIELD_IS_SANDBOXED = 10,
-	BOX_FUNC_FIELD_IS_NULL_CALL = 11,
-	BOX_FUNC_FIELD_OPTS = 12,
-	BOX_FUNC_FIELD_COMMENT = 13,
-	BOX_FUNC_FIELD_CREATED = 14,
-	BOX_FUNC_FIELD_LAST_ALTERED = 15
 };
 
 /** _collation fields. */
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index 4e17fbfea2a7521f0d4ff45950dd6fd49969f06b..b20dc41e5f5f8012142a5455f39c5a78a94b9fc1 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -53,12 +53,7 @@ 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'}, {'name': 'language', 'type': 'string'}, {'name': 'body',
-        'type': 'string'}, {'name': 'routine_type', 'type': 'string'}, {'name': 'data_type',
-        'type': 'map'}, {'name': 'sql_data_access', 'type': 'string'}, {'name': 'is_deterministic',
-        'type': 'boolean'}, {'name': 'is_sandboxed', 'type': 'boolean'}, {'name': 'is_null_call',
-        'type': 'boolean'}, {'name': 'opts', 'type': 'map'}, {'name': 'comment', 'type': 'string'},
-      {'name': 'created', 'type': 'string'}, {'name': 'last_altered', 'type': 'string'}]]
+        'type': 'unsigned'}]]
   - [297, 1, '_vfunc', 'sysview', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'setuid',
         'type': 'unsigned'}]]
@@ -155,10 +150,9 @@ box.space._user:select{}
   - [3, 1, 'replication', 'role', {}]
   - [31, 1, 'super', 'role', {}]
 ...
-for _, v in box.space._func:pairs{} do r = {} table.insert(r, v:update({{"=", 15, ""}, {"=", 16, ""}})) return r end
+box.space._func:select{}
 ---
-- - [1, 1, 'box.schema.user.info', 1, 'LUA', '', 'function', {}, 'none', false, false,
-    true, {}, '', '', '']
+- - [1, 1, 'box.schema.user.info', 1, 'LUA']
 ...
 box.space._priv:select{}
 ---
diff --git a/test/box-py/bootstrap.test.py b/test/box-py/bootstrap.test.py
index ba0689ae90fd4b7d1c68cec87eaec8c245922816..4f2f55a7c952213b50fd47eb4c345e1b1ff0842e 100644
--- a/test/box-py/bootstrap.test.py
+++ b/test/box-py/bootstrap.test.py
@@ -4,7 +4,7 @@ server.admin('box.space._cluster:select{}')
 server.admin('box.space._space:select{}')
 server.admin('box.space._index:select{}')
 server.admin('box.space._user:select{}')
-server.admin('for _, v in box.space._func:pairs{} do r = {} table.insert(r, v:update({{"=", 15, ""}, {"=", 16, ""}})) return r end')
+server.admin('box.space._func:select{}')
 server.admin('box.space._priv:select{}')
 
 # Cleanup
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index c3ed3a65d7a33787be850350c53116e49ab49e2e..53d3661067e60c17cfcc3a1436362c8ecfe7f1e6 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -793,12 +793,7 @@ 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'}, {'name': 'language', 'type': 'string'}, {'name': 'body',
-        'type': 'string'}, {'name': 'routine_type', 'type': 'string'}, {'name': 'data_type',
-        'type': 'map'}, {'name': 'sql_data_access', 'type': 'string'}, {'name': 'is_deterministic',
-        'type': 'boolean'}, {'name': 'is_sandboxed', 'type': 'boolean'}, {'name': 'is_null_call',
-        'type': 'boolean'}, {'name': 'opts', 'type': 'map'}, {'name': 'comment', 'type': 'string'},
-      {'name': 'created', 'type': 'string'}, {'name': 'last_altered', 'type': 'string'}]]
+        'type': 'unsigned'}]]
   - [297, 1, '_vfunc', 'sysview', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'setuid',
         'type': 'unsigned'}]]
@@ -834,8 +829,7 @@ box.space._space:select()
 ...
 box.space._func:select()
 ---
-- - [1, 1, 'box.schema.user.info', 1, 'LUA', '', 'function', {}, 'none', false, false,
-    true, {}, '', '2019-06-23 18:11:31', '2019-06-23 18:11:31']
+- - [1, 1, 'box.schema.user.info', 1, 'LUA']
 ...
 session = nil
 ---
diff --git a/test/box/function1.result b/test/box/function1.result
index 7bea2d64ddb6e925c7725d71ea822a364735265b..99006926e5ec245eabd768598592c7c3c5a0c5af 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -16,25 +16,7 @@ c = net.connect(os.getenv("LISTEN"))
 box.schema.func.create('function1', {language = "C"})
 ---
 ...
-function setmap(tab) return setmetatable(tab, { __serialize = 'map' }) end
----
-...
-datetime = os.date("%Y-%m-%d %H:%M:%S")
----
-...
-box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', 'procedure', setmap({}), 'none', false, false, true, setmap({}), '', datetime, datetime}
----
-- error: 'Failed to create function ''function1'': unsupported routine_type value'
-...
-box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', 'function', setmap({}), 'reads', false, false, true, setmap({}), '', datetime, datetime}
----
-- error: 'Failed to create function ''function1'': unsupported sql_data_access value'
-...
-box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', 'function', setmap({}), 'none', false, false, false, setmap({}), '', datetime, datetime}
----
-- error: 'Failed to create function ''function1'': unsupported is_null_call value'
-...
-box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', 'function', setmap({}), 'none', false, false, true, setmap({}), '', datetime, datetime}
+box.space._func:replace{2, 1, 'function1', 0, 'LUA'}
 ---
 - error: function does not support alter
 ...
@@ -77,11 +59,10 @@ c:call('function1.args', { 15 })
 ...
 box.func["function1.args"]
 ---
-- is_deterministic: false
-  id: 2
+- language: C
   setuid: false
   name: function1.args
-  language: C
+  id: 2
 ...
 box.func["function1.args"]:call()
 ---
@@ -345,7 +326,7 @@ c:close()
 function divide(a, b) return a / b end
 ---
 ...
-box.schema.func.create("divide", {comment = 'Divide two values'})
+box.schema.func.create("divide")
 ---
 ...
 func = box.func.divide
@@ -387,12 +368,10 @@ func:drop()
 ...
 func
 ---
-- is_deterministic: false
-  id: 2
+- language: LUA
   setuid: false
-  comment: Divide two values
   name: divide
-  language: LUA
+  id: 2
 ...
 func.drop()
 ---
@@ -445,11 +424,10 @@ func:drop()
 ...
 func
 ---
-- is_deterministic: false
-  id: 2
+- language: C
   setuid: false
   name: function1.divide
-  language: C
+  id: 2
 ...
 func:drop()
 ---
@@ -532,177 +510,6 @@ 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'': [string "return function(tuple)
-    ret tuple"]:1: ''='' expected near ''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()
----
-...
-test_run:cmd("push filter '(.builtin/.*.lua):[0-9]+' to '\\1'")
----
-- true
-...
-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 sandboxed 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()
----
-...
-sum = 0
----
-...
-function inc_g(val) sum = sum + val end
----
-...
-box.schema.func.create('call_inc_g', {body = "function(val) inc_g(val) end"})
----
-...
-box.func.call_inc_g:call({1})
----
-...
-assert(sum == 1)
----
-- true
-...
-box.schema.func.create('call_inc_g_safe', {body = "function(val) inc_g(val) end", is_sandboxed = true})
----
-...
-box.func.call_inc_g_safe:call({1})
----
-- error: '[string "return function(val) inc_g(val) end"]:1: attempt to call global
-    ''inc_g'' (a nil value)'
-...
-assert(sum == 1)
----
-- true
-...
-box.func.call_inc_g:drop()
----
-...
-box.func.call_inc_g_safe:drop()
----
-...
--- Test persistent function assemble corner cases
-box.schema.func.create('compiletime_tablef', {body = "{}"})
----
-- error: 'Failed to dynamically load function ''compiletime_tablef'': given body doesn''t
-    define a function'
-...
-box.schema.func.create('compiletime_call_inc_g', {body = "inc_g()"})
----
-- error: 'Failed to dynamically load function ''compiletime_call_inc_g'': [string
-    "return inc_g()"]:1: attempt to call global ''inc_g'' (a nil value)'
-...
-assert(sum == 1)
----
-- true
-...
 test_run:cmd("clear filter")
 ---
 - true
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index 243f78eb148405140d669bb5c9b9cc4aab898c0d..25966b91582a9385c1d013d16af9d5610bf72bd1 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -7,12 +7,7 @@ net = require('net.box')
 c = net.connect(os.getenv("LISTEN"))
 
 box.schema.func.create('function1', {language = "C"})
-function setmap(tab) return setmetatable(tab, { __serialize = 'map' }) end
-datetime = os.date("%Y-%m-%d %H:%M:%S")
-box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', 'procedure', setmap({}), 'none', false, false, true, setmap({}), '', datetime, datetime}
-box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', 'function', setmap({}), 'reads', false, false, true, setmap({}), '', datetime, datetime}
-box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', 'function', setmap({}), 'none', false, false, false, setmap({}), '', datetime, datetime}
-box.space._func:replace{2, 1, 'function1', 0, 'LUA', '', 'function', setmap({}), 'none', false, false, true, setmap({}), '', datetime, datetime}
+box.space._func:replace{2, 1, 'function1', 0, 'LUA'}
 box.schema.user.grant('guest', 'execute', 'function', 'function1')
 _ = box.schema.space.create('test')
 _ = box.space.test:create_index('primary')
@@ -125,7 +120,7 @@ c:close()
 
 -- Test registered functions interface.
 function divide(a, b) return a / b end
-box.schema.func.create("divide", {comment = 'Divide two values'})
+box.schema.func.create("divide")
 func = box.func.divide
 func.call({4, 2})
 func:call(4, 2)
@@ -185,68 +180,4 @@ 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()
-test_run:cmd("push filter '(.builtin/.*.lua):[0-9]+' to '\\1'")
-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 sandboxed 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()
-
-sum = 0
-function inc_g(val) sum = sum + val end
-box.schema.func.create('call_inc_g', {body = "function(val) inc_g(val) end"})
-box.func.call_inc_g:call({1})
-assert(sum == 1)
-box.schema.func.create('call_inc_g_safe', {body = "function(val) inc_g(val) end", is_sandboxed = true})
-box.func.call_inc_g_safe:call({1})
-assert(sum == 1)
-box.func.call_inc_g:drop()
-box.func.call_inc_g_safe:drop()
-
--- Test persistent function assemble corner cases
-box.schema.func.create('compiletime_tablef', {body = "{}"})
-box.schema.func.create('compiletime_call_inc_g', {body = "inc_g()"})
-assert(sum == 1)
-
 test_run:cmd("clear filter")