From: Vladimir Davydov <vdavydov.dev@gmail.com> To: imeevma@tarantool.org Cc: tarantool-patches@freelists.org Subject: Re: [PATCH v1 2/2] netbox: define formats for tuple from netbox Date: Tue, 11 Jun 2019 11:55:11 +0300 [thread overview] Message-ID: <20190611085511.jto7yyuognolqg3b@esperanza> (raw) In-Reply-To: <82d7940c4420df06b6e905304f27797d4d348329.1560160282.git.imeevma@gmail.com> On Mon, Jun 10, 2019 at 01:02:24PM +0300, imeevma@tarantool.org wrote: > This patch creates tuple_formats for the tuples obtained through > the netbox. > > Closes #2978 Please write a docbot request. > --- > src/box/lua/net_box.c | 87 ++++++++++++++++++++++++++++++++++++++++++++--- > src/box/lua/net_box.lua | 69 ++++++++++++++++++++++++++++--------- > test/box/net.box.result | 77 +++++++++++++++++++++++++++++++++++++++++ > test/box/net.box.test.lua | 20 +++++++++++ > 4 files changed, 231 insertions(+), 22 deletions(-) > > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c > index 7484a86..fab4a8b 100644 > --- a/src/box/lua/net_box.c > +++ b/src/box/lua/net_box.c > @@ -40,6 +40,7 @@ > #include "box/xrow.h" > #include "box/tuple.h" > #include "box/execute.h" > +#include "box/schema.h" > > #include "lua/msgpack.h" > #include "third_party/base64.h" > @@ -590,12 +591,12 @@ netbox_encode_execute(lua_State *L) > * @param data MessagePack. > */ > static void > -netbox_decode_data(struct lua_State *L, const char **data) > +netbox_decode_data(struct lua_State *L, const char **data, uint32_t format_id) I'd pass a pointer to tuple_format struct instead of format_id to this function. > { > + (void)format_id; This is clearly useless. > uint32_t count = mp_decode_array(data); > lua_createtable(L, count, 0); > - struct tuple_format *format = > - box_tuple_format_default(); > + struct tuple_format *format = tuple_format_by_id(format_id); > for (uint32_t j = 0; j < count; ++j) { > const char *begin = *data; > mp_next(data); > @@ -608,6 +609,74 @@ netbox_decode_data(struct lua_State *L, const char **data) > } > } > > +static int > +netbox_format_new(struct lua_State *L) > +{ > + if (lua_gettop(L) != 1 || lua_istable(L, 1) != 1) > + return luaL_error(L, "Bad params!"); Please print a "Usage: ..." message, like other functions do. > + > + uint32_t count = lua_objlen(L, 1); > + if (count == 0) { > + lua_pushinteger(L, box_tuple_format_default()->id); Please use tuple_format_runtime instead of box_tuple_format_default(), here and everywhere else. The latter is, after all, for modules. > + return 1; > + } > + size_t size = count * sizeof(struct field_def); > + struct region *region = &fiber()->gc; > + size_t region_svp = region_used(region); > + struct field_def *fields = region_alloc(region, size); > + if (fields == NULL) { > + diag_set(OutOfMemory, size, "region_alloc", "fields"); > + return luaT_error(L); > + } > + memset(fields, 0, size); Please init each field with field_def_default in the loop below instead of zeroing it. > + for (uint32_t i = 0; i < count; ++i) { > + lua_pushinteger(L, i + 1); > + lua_gettable(L, 1); > + > + lua_pushstring(L, "type"); > + lua_gettable(L, -2); > + size_t len; > + const char *type_name = lua_tolstring(L, -1, &len); > + lua_pop(L, 1); > + fields[i].type = field_type_by_name(type_name, len); > + > + lua_pushstring(L, "name"); > + lua_gettable(L, -2); > + const char *name = lua_tolstring(L, -1, &len); Strictly speaking, the name or type can be absent. The type can also be invalid. We should handle those situations gracefully. > + fields[i].name = region_alloc(region, len + 1); > + memcpy(fields[i].name, name, len); > + fields[i].name[len] = '\0'; > + lua_pop(L, 1); > + lua_pop(L, 1); > + } > + struct tuple_dictionary *dict = tuple_dictionary_new(fields, count); > + if (dict == NULL) { > + region_truncate(region, region_svp); > + return luaT_error(L); > + } > + > + struct tuple_format *format = > + tuple_format_new(&box_tuple_format_default()->vtab, NULL, NULL, > + 0, fields, count, 0, dict, false, false); Do we need really need to pass fields to this function? AFAIU tuple dictionary would be enough since we only need names. What happens if a field is nullable? > + assert(format != NULL); Strictly speaking tuple_format_new() may fail with OOM. Please handle it. > + tuple_format_ref(format); > + lua_pushinteger(L, format->id); Returning a cdata with gc set would look robuster to me - with an id it's pretty easy to leak an object. > + region_truncate(region, region_svp); > + return 1; > +} > + > +static int > +netbox_format_delete(struct lua_State *L) > +{ > + int32_t format_id = luaL_checkinteger(L, 1); > + if (format_id == box_tuple_format_default()->id) > + return 0; > + struct tuple_format *format = tuple_format_by_id(format_id); > + assert(format != NULL); > + tuple_format_unref(format); > + return 0; > +} > + > /** > * Decode Tarantool response body consisting of single > * IPROTO_DATA key into array of tuples. > @@ -619,6 +688,11 @@ netbox_decode_select(struct lua_State *L) > { > uint32_t ctypeid; > const char *data = *(const char **)luaL_checkcdata(L, 1, &ctypeid); > + uint32_t format_id; > + if (lua_gettop(L) == 2) > + format_id = luaL_checkinteger(L, 2); > + else > + format_id = box_tuple_format_default()->id; > assert(mp_typeof(*data) == MP_MAP); > uint32_t map_size = mp_decode_map(&data); > /* Until 2.0 body has no keys except DATA. */ > @@ -627,7 +701,7 @@ netbox_decode_select(struct lua_State *L) > uint32_t key = mp_decode_uint(&data); > assert(key == IPROTO_DATA); > (void) key; > - netbox_decode_data(L, &data); > + netbox_decode_data(L, &data, format_id); > *(const char **)luaL_pushcdata(L, ctypeid) = data; > return 2; > } > @@ -716,7 +790,8 @@ netbox_decode_execute(struct lua_State *L) > uint32_t key = mp_decode_uint(&data); > switch(key) { > case IPROTO_DATA: > - netbox_decode_data(L, &data); > + netbox_decode_data(L, &data, > + box_tuple_format_default()->id); > rows_index = i - map_size; > break; > case IPROTO_METADATA: > @@ -766,6 +841,8 @@ luaopen_net_box(struct lua_State *L) > { "communicate", netbox_communicate }, > { "decode_select", netbox_decode_select }, > { "decode_execute", netbox_decode_execute }, > + { "_format_new", netbox_format_new }, > + { "_format_delete", netbox_format_delete }, I think we better place these helpers in src/box/lua/misc.cc - we might need them somewhere else. Also, this is 'internal' namespace so no need to prefix function names with an underscore. Actually, I think we should add these functions in a separate patch and cover them with some basic tests involving argument checking (like passing an invalid field type or a field without a name). > { NULL, NULL} > }; > /* luaL_register_module polutes _G */ > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index 8d42fb4..26ff7ff 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -63,12 +63,22 @@ local function decode_data(raw_data) > local response, raw_end = decode(raw_data) > return response[IPROTO_DATA_KEY], raw_end > end > -local function decode_tuple(raw_data) > - local response, raw_end = internal.decode_select(raw_data) > +local function decode_tuple(raw_data, raw_data_end, opts) You use name 'args' when passing 'opts' to the encoder. Let's use name 'args' everywhere to avoid confusion. > + local response, raw_end > + if opts ~= nil and opts.format_id ~= nil then > + response, raw_end = internal.decode_select(raw_data, opts.format_id) > + else > + response, raw_end = internal.decode_select(raw_data) > + end > return response[1], raw_end > end > -local function decode_get(raw_data) > - local body, raw_end = internal.decode_select(raw_data) > +local function decode_get(raw_data, raw_data_end, opts) > + local body, raw_end > + if opts ~= nil and opts.format_id ~= nil then > + body, raw_end = internal.decode_select(raw_data, opts.format_id) > + else > + body, raw_end = internal.decode_select(raw_data) > + end Can the 'else' branch be executed at all? Shouldn't 'format' always be available for decode_tuple and decode_get? > if body[2] then > return nil, raw_end, box.error.MORE_THAN_ONE_TUPLE > end > @@ -82,6 +92,15 @@ local function decode_push(raw_data) > local response, raw_end = decode(raw_data) > return response[IPROTO_DATA_KEY][1], raw_end > end > +local function decode_select(raw_data, raw_data_end, opts) > + if opts ~= nil and opts.format_id ~= nil then > + return internal.decode_select(raw_data, opts.format_id) > + end > + return internal.decode_select(raw_data) > +end > +local function decode_execute(raw_data, raw_data_end) > + return internal.decode_execute(raw_data) > +end > > local function encode_call(send_buf, id, method_args) > return internal.encode_call(send_buf, id, method_args.func_name, > @@ -157,7 +176,7 @@ local method_encoder = { > > local method_decoder = { > ping = decode_nil, > - call_16 = internal.decode_select, > + call_16 = decode_select, I'd rather add decode_call_16 to avoid branching in decode_select (branching in a virtual method looks ugly IMO). > call_17 = decode_data, > eval = decode_data, > insert = decode_tuple, > @@ -165,8 +184,8 @@ local method_decoder = { > delete = decode_tuple, > update = decode_tuple, > upsert = decode_nil, > - select = internal.decode_select, > - execute = internal.decode_execute, > + select = decode_select, > + execute = decode_execute, > get = decode_get, > min = decode_get, > max = decode_get, > @@ -630,14 +649,15 @@ local function create_transport(host, port, user, password, callback, > -- Decode xrow.body[DATA] to Lua objects > if status == IPROTO_OK_KEY then > request.response, real_end, request.errno = > - method_decoder[request.method](body_rpos, body_end) > + method_decoder[request.method](body_rpos, body_end, > + request.method_args) > assert(real_end == body_end, "invalid body length") > requests[id] = nil > request.id = nil > else > local msg > msg, real_end, request.errno = > - method_decoder.push(body_rpos, body_end) > + method_decoder.push(body_rpos, body_end, request.method_args) Looks unnecessary. > assert(real_end == body_end, "invalid body length") > request.on_push(request.on_push_ctx, msg) > end > @@ -1085,6 +1105,14 @@ end > > function remote_methods:close() > check_remote_arg(self, 'close') > + if (self.space ~= nil and type(self.space) == 'table') then > + for _,space in pairs(self.space) do > + if space.format_id ~= nil then > + internal._format_delete(space._format_id) > + space.format_id = nil > + end > + end > + end > self._transport.stop() > end > > @@ -1274,6 +1302,7 @@ function remote_methods:_install_schema(schema_version, spaces, indices, > s.index = {} > s.temporary = false > s._format = format > + s._format_id = internal._format_new(format) Shouldn't you unref the old format before setting the new one? > s.connection = self > if #space > 5 then > local opts = space[6] > @@ -1391,13 +1420,15 @@ space_metatable = function(remote) > > function methods:insert(tuple, opts) > check_space_arg(self, 'insert') > - local method_args = {space_id=self.id, tuple=tuple} > + local method_args = {space_id=self.id, tuple=tuple, > + format_id=self._format_id} > return remote:_request('insert', opts, method_args) > end > > function methods:replace(tuple, opts) > check_space_arg(self, 'replace') > - local method_args = {space_id=self.id, tuple=tuple} > + local method_args = {space_id=self.id, tuple=tuple, > + format_id=self._format_id} > return remote:_request('replace', opts, method_args) > end > > @@ -1453,7 +1484,7 @@ index_metatable = function(remote) > local limit = tonumber(opts and opts.limit) or 0xFFFFFFFF > local method_args = {space_id=self.space.id, index_id=self.id, > iterator=iterator, offset=offset, limit=limit, > - key=key} > + key=key, format_id=self.space._format_id} > return (remote:_request('select', opts, method_args)) > end > > @@ -1463,7 +1494,8 @@ index_metatable = function(remote) > error("index:get() doesn't support `buffer` argument") > end > local method_args = {space_id=self.space.id, index_id=self.id, > - iterator=box.index.EQ, offset=0, limit=2, key=key} > + iterator=box.index.EQ, offset=0, limit=2, key=key, > + format_id=self.space._format_id} > return nothing_or_data(remote:_request('get', opts, method_args)) > end > > @@ -1473,7 +1505,8 @@ index_metatable = function(remote) > error("index:min() doesn't support `buffer` argument") > end > local method_args = {space_id=self.space.id, index_id=self.id, > - iterator=box.index.GE, offset=0, limit=1, key=key} > + iterator=box.index.GE, offset=0, limit=1, key=key, > + format_id=self.space._format_id} > return nothing_or_data(remote:_request('min', opts, method_args)) > end > > @@ -1483,7 +1516,8 @@ index_metatable = function(remote) > error("index:max() doesn't support `buffer` argument") > end > local method_args = {space_id=self.space.id, index_id=self.id, > - iterator=box.index.LE, offset=0, limit=1, key=key} > + iterator=box.index.LE, offset=0, limit=1, key=key, > + format_id=self.space._format_id} > return nothing_or_data(remote:_request('max', opts, method_args)) > end > > @@ -1500,14 +1534,15 @@ index_metatable = function(remote) > > function methods:delete(key, opts) > check_index_arg(self, 'delete') > - local method_args = {space_id=self.space.id, index_id=self.id, key=key} > + local method_args = {space_id=self.space.id, index_id=self.id, key=key, > + format_id=self.space._format_id} > return nothing_or_data(remote:_request('delete', opts, method_args)) > end > > function methods:update(key, oplist, opts) > check_index_arg(self, 'update') > local method_args = {space_id=self.space.id, index_id=self.id, key=key, > - oplist=oplist} > + oplist=oplist, format_id=self.space._format_id} > return nothing_or_data(remote:_request('update', opts, method_args)) > end > > diff --git a/test/box/net.box.result b/test/box/net.box.result > index 451c31d..da40a3d 100644 > --- a/test/box/net.box.result > +++ b/test/box/net.box.result > @@ -3572,6 +3572,83 @@ box.schema.func.drop('change_schema') > --- > ... > -- > +-- gh-2978: field names for tuples received from netbox. > +-- > +_ = box.schema.create_space("named", {format = {{name = "id"}, {name="abc"}}}) > +--- > +... > +_ = box.space.named:create_index('id', {parts = {{1, 'unsigned'}}}) > +--- > +... > +box.space.named:insert({1, 1}) > +--- > +- [1, 1] > +... > +box.schema.user.grant('guest', 'read, write, execute', 'universe') Please don't use 'universe' in tests - we're trying to deprecate it AFAIR. Grant specific permissions instead. It would be nice to check that schema reload does update the format, i.e. update the space format, call remote methods again and check that they do use new names. > +--- > +... > +cn = net.connect(box.cfg.listen) > +--- > +... > +s = cn.space.named > +--- > +... > +s:get{1}.id > +--- > +- 1 > +... > +s:get{1}:tomap() > +--- > +- 1: 1 > + 2: 1 > + abc: 1 > + id: 1 > +... > +s:insert{2,3}:tomap() > +--- > +- 1: 2 > + 2: 3 > + abc: 3 > + id: 2 > +... > +s:replace{2,14}:tomap() > +--- > +- 1: 2 > + 2: 14 > + abc: 14 > + id: 2 > +... > +s:update(1, {{'+', 2, 10}}):tomap() > +--- > +- 1: 1 > + 2: 11 > + abc: 11 > + id: 1 > +... > +s:select()[1]:tomap() > +--- > +- 1: 1 > + 2: 11 > + abc: 11 > + id: 1 > +... > +s:delete({2}):tomap() > +--- > +- 1: 2 > + 2: 14 > + abc: 14 > + id: 2 > +... > +cn:close() > +--- > +... > +box.space.named:drop() > +--- > +... > +box.schema.user.revoke('guest', 'read, write, execute', 'universe') > +--- > +... > +--
next prev parent reply other threads:[~2019-06-11 8:55 UTC|newest] Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top 2019-06-10 10:02 [PATCH v1 0/2] " imeevma 2019-06-10 10:02 ` [PATCH v1 1/2] netbox: store method_encoder args in request imeevma 2019-06-11 8:15 ` Vladimir Davydov 2019-06-14 11:50 ` Mergen Imeev 2019-06-18 8:38 ` Vladimir Davydov 2019-06-10 10:02 ` [PATCH v1 2/2] netbox: define formats for tuple from netbox imeevma 2019-06-11 8:55 ` Vladimir Davydov [this message] 2019-06-14 12:29 ` Mergen Imeev 2019-06-18 8:55 ` Vladimir Davydov 2019-06-18 9:00 ` Vladimir Davydov 2019-06-11 8:55 ` [PATCH v1 0/2] " Vladimir Davydov 2019-06-14 12:32 ` Mergen Imeev
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20190611085511.jto7yyuognolqg3b@esperanza \ --to=vdavydov.dev@gmail.com \ --cc=imeevma@tarantool.org \ --cc=tarantool-patches@freelists.org \ --subject='Re: [PATCH v1 2/2] netbox: define formats for tuple from netbox' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox