diff --git a/extra/exports b/extra/exports index b61f164167ab169a4e4c554763a7fed93e533b30..549f33f3e7363d0994597266628afa46b678c763 100644 --- a/extra/exports +++ b/extra/exports @@ -59,6 +59,7 @@ box_error_last box_error_message box_error_set box_error_type +box_generate_func_id box_ibuf_read_range box_ibuf_reserve box_ibuf_write_range diff --git a/src/box/box.cc b/src/box/box.cc index 3e901de6e84c47d07bf24559488dca70cc869c4f..f15001442225a7c58e62c6c5308e2ee4e2516a13 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -30,6 +30,8 @@ */ #include "box/box.h" +#include "func_cache.h" +#include "schema_def.h" #include "trivia/config.h" #include <sys/utsname.h> @@ -5365,15 +5367,9 @@ box_read_ffi_enable(void) box_read_ffi_is_disabled = false; } -int -box_generate_space_id(uint32_t *new_space_id, bool is_temporary) +static int +next_u32_id(uint32_t space_id, uint32_t id_range_end, uint32_t *max_id) { - assert(new_space_id != NULL); - uint32_t id_range_begin = !is_temporary ? - BOX_SYSTEM_ID_MAX + 1 : BOX_SPACE_ID_TEMPORARY_MIN; - uint32_t id_range_end = !is_temporary ? - (uint32_t)BOX_SPACE_ID_TEMPORARY_MIN : - (uint32_t)BOX_SPACE_MAX + 1; char key_buf[16]; char *key_end = key_buf; key_end = mp_encode_array(key_end, 1); @@ -5383,7 +5379,7 @@ box_generate_space_id(uint32_t *new_space_id, bool is_temporary) auto guard = make_scoped_guard([=] { fiber_set_user(fiber(), orig_credentials); }); - box_iterator_t *it = box_index_iterator(BOX_SPACE_ID, 0, ITER_LT, + box_iterator_t *it = box_index_iterator(space_id, 0, ITER_LT, key_buf, key_end); if (it == NULL) return -1; @@ -5393,9 +5389,25 @@ box_generate_space_id(uint32_t *new_space_id, bool is_temporary) if (rc != 0) return -1; assert(res != NULL); - uint32_t max_id = 0; - rc = tuple_field_u32(res, 0, &max_id); + rc = tuple_field_u32(res, 0, max_id); assert(rc == 0); + return 0; +} + +int +box_generate_space_id(uint32_t *new_space_id, bool is_temporary) +{ + if (box_check_configured() < 0) + return -1; + assert(new_space_id != NULL); + uint32_t id_range_begin = !is_temporary ? + BOX_SYSTEM_ID_MAX + 1 : BOX_SPACE_ID_TEMPORARY_MIN; + uint32_t id_range_end = !is_temporary ? + (uint32_t)BOX_SPACE_ID_TEMPORARY_MIN : + (uint32_t)BOX_SPACE_MAX + 1; + uint32_t max_id = 0; + if (next_u32_id(BOX_SPACE_ID, id_range_end, &max_id) != 0) + return -1; if (max_id < id_range_begin) max_id = id_range_begin - 1; *new_space_id = space_cache_find_next_unused_id(max_id); @@ -5415,6 +5427,35 @@ box_generate_space_id(uint32_t *new_space_id, bool is_temporary) return 0; } +API_EXPORT int +box_generate_func_id(uint32_t *new_func_id, bool use_reserved_range) +{ + if (box_check_configured() < 0) + return -1; + uint32_t id_range_begin = use_reserved_range ? + BOX_FUNCTION_RESERVED_MIN : 1; + uint32_t id_range_end = use_reserved_range ? + (uint32_t)BOX_FUNCTION_MAX + 1 : + (uint32_t)BOX_FUNCTION_RESERVED_MIN; + uint32_t max_id = 0; + if (next_u32_id(BOX_FUNC_ID, id_range_end, &max_id) != 0) + return -1; + if (max_id < id_range_begin) + max_id = id_range_begin - 1; + *new_func_id = func_cache_find_next_unused_id(id_range_begin - 1); + /* Try again if overflowed. */ + if (*new_func_id >= id_range_end) { + *new_func_id = + func_cache_find_next_unused_id(id_range_begin - 1); + /* Second overflow means all ids are occupied. */ + if (*new_func_id >= id_range_end) { + diag_set(ClientError, ER_CANT_GENERATE, "function id"); + return -1; + } + } + return 0; +} + static void on_garbage_collection(void) { diff --git a/src/box/box.h b/src/box/box.h index 216dc89319682675690b4595eb55b7af432b862f..b1b115acd10eadf6d9508c93828978e7478fd136 100644 --- a/src/box/box.h +++ b/src/box/box.h @@ -834,6 +834,10 @@ boxk(int type, uint32_t space_id, const char *format, ...); int box_generate_space_id(uint32_t *new_space_id, bool is_temporary); +/** Generate unique id for a function. */ +API_EXPORT int +box_generate_func_id(uint32_t *new_func_id, bool use_reserved_range); + /** * Broadcast the identification of the instance */ diff --git a/src/box/errcode.h b/src/box/errcode.h index 52cbbd7284448327325b2d5d91da66b24b607262..1a6015a32414f275d69563ed8b9670a5e1812b26 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -322,7 +322,7 @@ struct errcode_record { /*267 */_(ER_EXCEEDED_VDBE_MAX_STEPS, "Reached a limit on max executed vdbe opcodes. Limit: %u") \ /*268 */_(ER_ILLEGAL_OPTIONS, "Illegal options: %s") \ /*269 */_(ER_ILLEGAL_OPTIONS_FORMAT, "Each option in third argument must be a table containing only one key value pair") \ - /*270 */_(ER_UNUSED4, "") \ + /*270 */_(ER_CANT_GENERATE, "Can't generate %s") \ /*271 */_(ER_UNUSED5, "") \ /*272 */_(ER_SCHEMA_UPGRADE_IN_PROGRESS, "Schema upgrade is already in progress") \ /*273 */_(ER_UNUSED7, "") \ diff --git a/src/box/func_cache.c b/src/box/func_cache.c index 1a465642d10e58e81196fec0968df9a025efd0f8..4e48f34c34fad7fb42c2ce275bcf97bc7c19a9b1 100644 --- a/src/box/func_cache.c +++ b/src/box/func_cache.c @@ -7,6 +7,7 @@ #include <assert.h> #include "assoc.h" +#include "schema_def.h" /** ID -> func dictionary. */ static struct mh_i32ptr_t *funcs; @@ -87,6 +88,16 @@ func_by_name(const char *name, uint32_t name_len) return (struct func *)mh_strnptr_node(funcs_by_name, func)->val; } +uint32_t +func_cache_find_next_unused_id(uint32_t cur_id) +{ + for (cur_id++; cur_id <= BOX_FUNCTION_MAX; cur_id++) { + if (func_by_id(cur_id) == NULL) + return cur_id; + } + return cur_id; +} + void func_pin(struct func *func, struct func_cache_holder *holder, enum func_holder_type type) diff --git a/src/box/func_cache.h b/src/box/func_cache.h index b95138165e38d840b57da3c9901470426716c73c..1c4a3bc28f45cd7060a9ae05e60b3b38956f4d1d 100644 --- a/src/box/func_cache.h +++ b/src/box/func_cache.h @@ -90,6 +90,13 @@ func_by_id(uint32_t fid); struct func * func_by_name(const char *name, uint32_t name_len); +/** + * Find minimal unused id, which is greater than cur_id. + * If there is no available id, BOX_FUNCTION_MAX + 1 is returned. + */ +uint32_t +func_cache_find_next_unused_id(uint32_t cur_id); + /** * Register that there is a @a holder of type @a type that is dependent * on function @a func. diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc index 3092d03aea47887918cbe407bf05552e9d71744a..377f77f416498c98b42d1f9f5d2cfdf8aeb6aa3b 100644 --- a/src/box/lua/misc.cc +++ b/src/box/lua/misc.cc @@ -235,6 +235,20 @@ lbox_generate_space_id(lua_State *L) return 1; } +/** Generate unique id for a function. */ +static int +lbox_generate_func_id(lua_State *L) +{ + assert(lua_gettop(L) >= 1); + assert(lua_isboolean(L, 1) == 1); + bool use_reserved_range = lua_toboolean(L, 1) != 0; + uint32_t ret = 0; + if (box_generate_func_id(&ret, use_reserved_range) != 0) + return luaT_error(L); + lua_pushnumber(L, ret); + return 1; +} + /* }}} */ /** {{{ Helper that generates user auth data. **/ @@ -541,6 +555,7 @@ box_lua_misc_init(struct lua_State *L) {"read_view_list", lbox_read_view_list}, {"read_view_status", lbox_read_view_status}, {"generate_space_id", lbox_generate_space_id}, + {"generate_func_id", lbox_generate_func_id}, {NULL, NULL} }; diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 4c8f0f502656fdee4601917dc2f09755455ce54b..e057cc36c728c0ee1564074f052d4a715db5a682 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -3074,7 +3074,8 @@ end box.schema.func = {} box.schema.func.create = function(name, opts) opts = opts or {} - check_param_table(opts, { setuid = 'boolean', + check_param_table(opts, { id = 'number', + setuid = 'boolean', if_not_exists = 'boolean', language = 'string', body = 'string', is_deterministic = 'boolean', @@ -3108,12 +3109,18 @@ box.schema.func.create = function(name, opts) if opts.takes_raw_args then opts.opts.takes_raw_args = opts.takes_raw_args end - _func:auto_increment{session.euid(), name, opts.setuid, opts.language, - opts.body, opts.routine_type, opts.param_list, - opts.returns, opts.aggregate, opts.sql_data_access, - opts.is_deterministic, opts.is_sandboxed, - opts.is_null_call, opts.exports, opts.opts, - opts.comment, opts.created, opts.last_altered} + if opts.id == nil then + opts.id = internal.generate_func_id(false) + if opts.id == nil then + box.error() + end + end + _func:insert{opts.id, session.euid(), name, opts.setuid, opts.language, + opts.body, opts.routine_type, opts.param_list, + opts.returns, opts.aggregate, opts.sql_data_access, + opts.is_deterministic, opts.is_sandboxed, + opts.is_null_call, opts.exports, opts.opts, + opts.comment, opts.created, opts.last_altered} end box.schema.func.drop = function(name, opts) diff --git a/src/box/schema_def.h b/src/box/schema_def.h index 8380701bc08c69b3961274e45c668f414050171f..5f964cb4f8c8944191782bdf71dd3b426d478f0c 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -40,7 +40,7 @@ extern "C" { enum { BOX_ENGINE_MAX = 3, /* + 1 to the actual number of engines */ BOX_SPACE_MAX = INT32_MAX, - BOX_FUNCTION_MAX = 32000, + BOX_FUNCTION_MAX = INT32_MAX, BOX_INDEX_MAX = 128, BOX_NAME_MAX = 65000, BOX_INVALID_NAME_MAX = 64, @@ -65,6 +65,13 @@ enum { * choose an id from outside this range. */ BOX_SPACE_ID_TEMPORARY_MIN = (1 << 30), + /** + * Start of the range of reserved function ids. By default functions get + * ids from the default range (smaller than BOX_FUNCTION_RESERVED_MIN), + * but the user is free to choose an id from the reserved range + * explicitly. + */ + BOX_FUNCTION_RESERVED_MIN = 32001, }; static_assert(BOX_INVALID_NAME_MAX <= BOX_NAME_MAX, "invalid name max is less than name max"); diff --git a/test/box/access.result b/test/box/access.result index fc4f7478bd0685a4edb46c9380948ca20185d3d3..87bcd1f6630a7a824cc48b0dd95aceacdba8dc69 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -1302,7 +1302,7 @@ box.schema.user.create('test') ... box.schema.func.create('test') --- -- error: Read access to space '_func' is denied for user 'guest' +- error: Write access to space '_func' is denied for user 'guest' ... box.session.su('admin') --- @@ -1508,7 +1508,7 @@ box.schema.user.create('test_user') ... box.schema.func.create('test_func') --- -- error: Read access to space '_func' is denied for user 'tester' +- error: Write access to space '_func' is denied for user 'tester' ... box.session.su("admin") --- diff --git a/test/box/access_misc.result b/test/box/access_misc.result index be2464104f95db6613121d802b2d4f1972198f1f..69dd2702cc4d7ce44406c627bad9a421627e558c 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -181,7 +181,7 @@ gs = box.schema.space.create('guest_space') -- box.schema.func.create('guest_func') --- -- error: Read access to space '_func' is denied for user 'guest' +- error: Write access to space '_func' is denied for user 'guest' ... session.su('admin', box.schema.user.grant, "guest", "read", "universe") --- diff --git a/test/box/error.result b/test/box/error.result index 9c94062bdd7e2b571763d0d04b223ce304508871..fc54b7901b8ec83c46ca348e8cde5a4bb8429e24 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -488,6 +488,7 @@ t; | 267: box.error.EXCEEDED_VDBE_MAX_STEPS | 268: box.error.ILLEGAL_OPTIONS | 269: box.error.ILLEGAL_OPTIONS_FORMAT + | 270: box.error.CANT_GENERATE | 272: box.error.SCHEMA_UPGRADE_IN_PROGRESS | 274: box.error.UNCONFIGURED | ... diff --git a/test/box/function1.result b/test/box/function1.result index eef8c6fe1a545443e1e32380e223a4104fa7a68d..10275dba79dab2a9ca4a42e109013c014b50c2fd 100644 --- a/test/box/function1.result +++ b/test/box/function1.result @@ -97,7 +97,7 @@ box.func["function1.args"] exports: lua: true sql: false - id: 66 + id: 2 takes_raw_args: false setuid: false is_multikey: false @@ -589,7 +589,7 @@ func exports: lua: true sql: false - id: 66 + id: 2 takes_raw_args: false setuid: false is_multikey: false @@ -662,7 +662,7 @@ func exports: lua: true sql: false - id: 66 + id: 2 takes_raw_args: false setuid: false is_multikey: false diff --git a/test/sql-luatest/func_id_test.lua b/test/sql-luatest/func_id_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..c66beab21e9292584fa8b0dd717a4bf80cac95c5 --- /dev/null +++ b/test/sql-luatest/func_id_test.lua @@ -0,0 +1,73 @@ +local server = require('luatest.server') +local t = require('luatest') + +local g = t.group() + +g.before_all(function() + g.server = server:new({alias = 'func_id'}) + g.server:start() +end) + +g.after_all(function() + g.server:stop() +end) + +g.test_func_from_reserved_range = function() + g.server:exec(function() + local id = box.internal.generate_func_id(true) + t.assert(id > 32000) + local def = {language = 'LUA', + body = 'function() return 1 end', + id = id} + local _, err = box.schema.func.create('abc', def) + t.assert(err == nil) + local next_id = box.internal.generate_func_id(true) + t.assert(next_id == id + 1) + _, err = box.schema.func.drop(id) + t.assert(err == nil) + end) +end + +g.test_func_from_default_range = function() + g.server:exec(function() + local id = box.internal.generate_func_id(false) + t.assert(id <= 32000) + local def = {language = 'LUA', body = 'function() return 1 end' } + local _, err = box.schema.func.create('abc', def) + t.assert(err == nil) + local next_id = box.internal.generate_func_id(false) + t.assert(next_id == id + 1) + _, err = box.schema.func.drop(id) + t.assert(err == nil) + end) +end + +g.test_ffi_reserved_range = function() + g.server:exec(function() + local id = box.internal.generate_func_id(true) + local ffi = require('ffi') + ffi.cdef([[int box_generate_func_id( + uint32_t *new_func_id, + bool use_reserved_range + );]]) + local ptr = ffi.new('uint32_t[1]') + local res = ffi.C.box_generate_func_id(ptr, true) + t.assert(res == 0) + t.assert(ptr[0] == id) + end) +end + +g.test_ffi_default_range = function() + g.server:exec(function() + local id = box.internal.generate_func_id(false) + local ffi = require('ffi') + ffi.cdef([[int box_generate_func_id( + uint32_t *new_func_id, + bool use_reserved_range + );]]) + local ptr = ffi.new('uint32_t[1]') + local res = ffi.C.box_generate_func_id(ptr, false) + t.assert(res == 0) + t.assert(ptr[0] == id) + end) +end diff --git a/test/wal_off/func_max.result b/test/wal_off/func_max.result index a3ab5b431c99d32cbc9101be87d2575b23c800fd..bc0abb55196b386032d20744a40766b11c9b995a 100644 --- a/test/wal_off/func_max.result +++ b/test/wal_off/func_max.result @@ -42,11 +42,11 @@ test_run:cmd("setopt delimiter ''"); ... func_limit() --- -- error: 'Failed to create function ''func31936'': function id is too big' +- error: Can't generate function id ... drop_limit_func() --- -- error: Function 'func31936' does not exist +- error: Function 'func31999' does not exist ... box.schema.user.create('testuser') --- @@ -62,11 +62,11 @@ session.su('testuser') ... func_limit() --- -- error: 'Failed to create function ''func31936'': function id is too big' +- error: Can't generate function id ... drop_limit_func() --- -- error: Function 'func31936' does not exist +- error: Function 'func31999' does not exist ... session.su('admin') ---