Tarantool development patches archive
 help / color / mirror / Atom feed
* [PATCH v1 0/2] netbox: define formats for tuple from netbox
@ 2019-06-10 10:02 imeevma
  2019-06-10 10:02 ` [PATCH v1 1/2] netbox: store method_encoder args in request imeevma
                   ` (2 more replies)
  0 siblings, 3 replies; 12+ messages in thread
From: imeevma @ 2019-06-10 10:02 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: tarantool-patches

This patch defines field names for the tuples obtained through
the netbox.

Mergen Imeev (2):
  netbox: store method_encoder args in request
  netbox: define formats for tuple from netbox

 src/box/lua/net_box.c     |  87 ++++++++++++++++-
 src/box/lua/net_box.lua   | 232 +++++++++++++++++++++++++++++++++-------------
 test/box/access.result    |  15 ++-
 test/box/access.test.lua  |   9 +-
 test/box/net.box.result   |  83 ++++++++++++++++-
 test/box/net.box.test.lua |  26 +++++-
 6 files changed, 369 insertions(+), 83 deletions(-)

-- 
2.7.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v1 1/2] netbox: store method_encoder args in request
  2019-06-10 10:02 [PATCH v1 0/2] netbox: define formats for tuple from netbox imeevma
@ 2019-06-10 10:02 ` imeevma
  2019-06-11  8:15   ` Vladimir Davydov
  2019-06-10 10:02 ` [PATCH v1 2/2] netbox: define formats for tuple from netbox imeevma
  2019-06-11  8:55 ` [PATCH v1 0/2] " Vladimir Davydov
  2 siblings, 1 reply; 12+ messages in thread
From: imeevma @ 2019-06-10 10:02 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: tarantool-patches

This patch changes the way arguments are passed to functions in
the array method_encoder, and saves these arguments in REQUEST.
This is necessary to establish a connection between netbox space
objects and the tuples obtained through the netbox

Needed for #2978
---
 src/box/lua/net_box.lua   | 179 +++++++++++++++++++++++++++++++---------------
 test/box/access.result    |  15 +++-
 test/box/access.test.lua  |   9 ++-
 test/box/net.box.result   |   6 +-
 test/box/net.box.test.lua |   6 +-
 5 files changed, 146 insertions(+), 69 deletions(-)

diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 251ad40..8d42fb4 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -25,7 +25,6 @@ local check_primary_index = box.internal.check_primary_index
 
 local communicate     = internal.communicate
 local encode_auth     = internal.encode_auth
-local encode_select   = internal.encode_select
 local decode_greeting = internal.decode_greeting
 
 local TIMEOUT_INFINITY = 500 * 365 * 86400
@@ -84,22 +83,70 @@ local function decode_push(raw_data)
     return response[IPROTO_DATA_KEY][1], raw_end
 end
 
+local function encode_call(send_buf, id, method_args)
+    return internal.encode_call(send_buf, id, method_args.func_name,
+                                method_args.args)
+end
+local function encode_call_16(send_buf, id, method_args)
+    return internal.encode_call_16(send_buf, id, method_args.func_name,
+                                   method_args.args)
+end
+local function encode_ping(send_buf, id, ...)
+    return internal.encode_ping(send_buf, id, ...)
+end
+local function encode_eval(send_buf, id, method_args)
+    return internal.encode_eval(send_buf, id, method_args.code,
+                                method_args.args)
+end
+local function encode_insert(send_buf, id, method_args)
+    return internal.encode_insert(send_buf, id, method_args.space_id,
+                                  method_args.tuple)
+end
+local function encode_replace(send_buf, id, method_args)
+    return internal.encode_replace(send_buf, id, method_args.space_id,
+                                   method_args.tuple)
+end
+local function encode_delete(send_buf, id, method_args)
+    return internal.encode_delete(send_buf, id, method_args.space_id,
+                                  method_args.index_id, method_args.key)
+end
+local function encode_update(send_buf, id, method_args)
+    return internal.encode_update(send_buf, id, method_args.space_id,
+                                  method_args.index_id, method_args.key,
+                                  method_args.oplist)
+end
+local function encode_upsert(send_buf, id, method_args)
+    return internal.encode_upsert(send_buf, id, method_args.space_id,
+                                  method_args.key, method_args.oplist)
+end
+local function encode_select(send_buf, id, method_args)
+    return internal.encode_select(send_buf, id, method_args.space_id,
+                                  method_args.index_id, method_args.iterator,
+                                  method_args.offset, method_args.limit,
+                                  method_args.key)
+end
+local function encode_execute(send_buf, id, method_args)
+    return internal.encode_execute(send_buf, id, method_args.query,
+                                   method_args.parameters, method_args.sql_opts)
+end
+
+
 local method_encoder = {
-    ping    = internal.encode_ping,
-    call_16 = internal.encode_call_16,
-    call_17 = internal.encode_call,
-    eval    = internal.encode_eval,
-    insert  = internal.encode_insert,
-    replace = internal.encode_replace,
-    delete  = internal.encode_delete,
-    update  = internal.encode_update,
-    upsert  = internal.encode_upsert,
-    select  = internal.encode_select,
-    execute = internal.encode_execute,
-    get     = internal.encode_select,
-    min     = internal.encode_select,
-    max     = internal.encode_select,
-    count   = internal.encode_call,
+    ping    = encode_ping,
+    call_16 = encode_call_16,
+    call_17 = encode_call,
+    eval    = encode_eval,
+    insert  = encode_insert,
+    replace = encode_replace,
+    delete  = encode_delete,
+    update  = encode_update,
+    upsert  = encode_upsert,
+    select  = encode_select,
+    execute = encode_execute,
+    get     = encode_select,
+    min     = encode_select,
+    max     = encode_select,
+    count   = encode_call,
     -- inject raw data into connection, used by console and tests
     inject = function(buf, id, bytes)
         local ptr = buf:reserve(#bytes)
@@ -486,7 +533,7 @@ local function create_transport(host, port, user, password, callback,
     -- @retval not nil Future object.
     --
     local function perform_async_request(buffer, skip_header, method, on_push,
-                                         on_push_ctx, ...)
+                                         on_push_ctx, method_args)
         if state ~= 'active' and state ~= 'fetch_schema' then
             return nil, box.error.new({code = last_errno or E_NO_CONNECTION,
                                        reason = last_error})
@@ -497,12 +544,12 @@ local function create_transport(host, port, user, password, callback,
             worker_fiber:wakeup()
         end
         local id = next_request_id
-        method_encoder[method](send_buf, id, ...)
+        method_encoder[method](send_buf, id, method_args)
         next_request_id = next_id(id)
-        -- Request in most cases has maximum 9 members:
+        -- Request in most cases has maximum 10 members:
         -- method, buffer, skip_header, id, cond, errno, response,
-        -- on_push, on_push_ctx.
-        local request = setmetatable(table_new(0, 9), request_mt)
+        -- on_push, on_push_ctx and method_args.
+        local request = setmetatable(table_new(0, 10), request_mt)
         request.method = method
         request.buffer = buffer
         request.skip_header = skip_header
@@ -511,6 +558,7 @@ local function create_transport(host, port, user, password, callback,
         requests[id] = request
         request.on_push = on_push
         request.on_push_ctx = on_push_ctx
+        request.method_args = method_args
         return request
     end
 
@@ -520,10 +568,10 @@ local function create_transport(host, port, user, password, callback,
     -- @retval not nil Response object.
     --
     local function perform_request(timeout, buffer, skip_header, method,
-                                   on_push, on_push_ctx, ...)
+                                   on_push, on_push_ctx, method_args)
         local request, err =
             perform_async_request(buffer, skip_header, method, on_push,
-                                  on_push_ctx, ...)
+                                  on_push_ctx, method_args)
         if not request then
             return nil, err
         end
@@ -739,13 +787,14 @@ local function create_transport(host, port, user, password, callback,
         local select3_id = new_request_id()
         local response = {}
         -- fetch everything from space _vspace, 2 = ITER_ALL
-        encode_select(send_buf, select1_id, VSPACE_ID, 0, 2, 0, 0xFFFFFFFF, nil)
+        internal.encode_select(send_buf, select1_id, VSPACE_ID, 0, 2, 0,
+                               0xFFFFFFFF, nil)
         -- fetch everything from space _vindex, 2 = ITER_ALL
-        encode_select(send_buf, select2_id, VINDEX_ID, 0, 2, 0, 0xFFFFFFFF, nil)
+        internal.encode_select(send_buf, select2_id, VINDEX_ID, 0, 2, 0,
+                               0xFFFFFFFF, nil)
         -- fetch everything from space _vcollation, 2 = ITER_ALL
-        encode_select(send_buf, select3_id, VCOLLATION_ID, 0, 2, 0, 0xFFFFFFFF,
-                      nil)
-
+        internal.encode_select(send_buf, select3_id, VCOLLATION_ID, 0, 2, 0,
+                               0xFFFFFFFF, nil)
         schema_version = nil -- any schema_version will do provided that
                              -- it is consistent across responses
         repeat
@@ -1064,7 +1113,7 @@ function remote_methods:wait_connected(timeout)
     return self._transport.wait_state('active', timeout)
 end
 
-function remote_methods:_request(method, opts, ...)
+function remote_methods:_request(method, opts, method_args)
     local transport = self._transport
     local on_push, on_push_ctx, buffer, skip_header, deadline
     -- Extract options, set defaults, check if the request is
@@ -1078,7 +1127,7 @@ function remote_methods:_request(method, opts, ...)
             end
             local res, err =
                 transport.perform_async_request(buffer, skip_header, method,
-                                                table.insert, {}, ...)
+                                                table.insert, {}, method_args)
             if err then
                 box.error(err)
             end
@@ -1106,7 +1155,7 @@ function remote_methods:_request(method, opts, ...)
     end
     local res, err = transport.perform_request(timeout, buffer, skip_header,
                                                method, on_push, on_push_ctx,
-                                               ...)
+                                               method_args)
     if err then
         box.error(err)
     end
@@ -1133,14 +1182,16 @@ end
 -- @deprecated since 1.7.4
 function remote_methods:call_16(func_name, ...)
     check_remote_arg(self, 'call')
-    return (self:_request('call_16', nil, tostring(func_name), {...}))
+    local method_args = {func_name=tostring(func_name), args={...}}
+    return (self:_request('call_16', nil, method_args))
 end
 
 function remote_methods:call(func_name, args, opts)
     check_remote_arg(self, 'call')
     check_call_args(args)
     args = args or {}
-    local res = self:_request('call_17', opts, tostring(func_name), args)
+    local method_args = {func_name=tostring(func_name), args=args}
+    local res = self:_request('call_17', opts, method_args)
     if type(res) ~= 'table' or opts and opts.is_async then
         return res
     end
@@ -1150,14 +1201,16 @@ end
 -- @deprecated since 1.7.4
 function remote_methods:eval_16(code, ...)
     check_remote_arg(self, 'eval')
-    return unpack((self:_request('eval', nil, code, {...})))
+    local method_args = {code=code, args={...}}
+    return unpack((self:_request('eval', nil, method_args)))
 end
 
 function remote_methods:eval(code, args, opts)
     check_remote_arg(self, 'eval')
     check_eval_args(args)
     args = args or {}
-    local res = self:_request('eval', opts, code, args)
+    local method_args = {code=code, args=args}
+    local res = self:_request('eval', opts, method_args)
     if type(res) ~= 'table' or opts and opts.is_async then
         return res
     end
@@ -1169,8 +1222,9 @@ function remote_methods:execute(query, parameters, sql_opts, netbox_opts)
     if sql_opts ~= nil then
         box.error(box.error.UNSUPPORTED, "execute", "options")
     end
-    return self:_request('execute', netbox_opts, query, parameters or {},
-                         sql_opts or {})
+    local method_args = {query=query, parameters=parameters or {},
+                         sql_opts=sql_opts or {}}
+    return self:_request('execute', netbox_opts, method_args)
 end
 
 function remote_methods:wait_state(state, timeout)
@@ -1314,7 +1368,8 @@ function console_methods:eval(line, timeout)
     end
     if self.protocol == 'Binary' then
         local loader = 'return require("console").eval(...)'
-        res, err = pr(timeout, nil, false, 'eval', nil, nil, loader, {line})
+        local method_args = {code=loader, args={line}}
+        res, err = pr(timeout, nil, false, 'eval', nil, nil, method_args)
     else
         assert(self.protocol == 'Lua console')
         res, err = pr(timeout, nil, false, 'inject', nil, nil, line..'$EOF$\n')
@@ -1336,33 +1391,38 @@ space_metatable = function(remote)
 
     function methods:insert(tuple, opts)
         check_space_arg(self, 'insert')
-        return remote:_request('insert', opts, self.id, tuple)
+        local method_args = {space_id=self.id, tuple=tuple}
+        return remote:_request('insert', opts, method_args)
     end
 
     function methods:replace(tuple, opts)
         check_space_arg(self, 'replace')
-        return remote:_request('replace', opts, self.id, tuple)
+        local method_args = {space_id=self.id, tuple=tuple}
+        return remote:_request('replace', opts, method_args)
     end
 
     function methods:select(key, opts)
         check_space_arg(self, 'select')
+        local method_args = {key=key, opts=opts}
         return check_primary_index(self):select(key, opts)
     end
 
     function methods:delete(key, opts)
         check_space_arg(self, 'delete')
+        local method_args = {key=key, opts=opts}
         return check_primary_index(self):delete(key, opts)
     end
 
     function methods:update(key, oplist, opts)
         check_space_arg(self, 'update')
+        local method_args = {oplist=oplist, opts=opts}
         return check_primary_index(self):update(key, oplist, opts)
     end
 
     function methods:upsert(key, oplist, opts)
         check_space_arg(self, 'upsert')
-        return nothing_or_data(remote:_request('upsert', opts, self.id, key,
-                                               oplist))
+        local method_args = {space_id=self.id, key=key, oplist=oplist}
+        return nothing_or_data(remote:_request('upsert', opts, method_args))
     end
 
     function methods:get(key, opts)
@@ -1391,8 +1451,10 @@ index_metatable = function(remote)
         local iterator = check_iterator_type(opts, key_is_nil)
         local offset = tonumber(opts and opts.offset) or 0
         local limit = tonumber(opts and opts.limit) or 0xFFFFFFFF
-        return (remote:_request('select', opts, self.space.id, self.id,
-                                iterator, offset, limit, key))
+        local method_args = {space_id=self.space.id, index_id=self.id,
+                             iterator=iterator, offset=offset, limit=limit,
+                             key=key}
+        return (remote:_request('select', opts, method_args))
     end
 
     function methods:get(key, opts)
@@ -1400,8 +1462,9 @@ index_metatable = function(remote)
         if opts and opts.buffer then
             error("index:get() doesn't support `buffer` argument")
         end
-        return nothing_or_data(remote:_request('get', opts, self.space.id,
-                               self.id, box.index.EQ, 0, 2, key))
+        local method_args = {space_id=self.space.id, index_id=self.id,
+                             iterator=box.index.EQ, offset=0, limit=2, key=key}
+        return nothing_or_data(remote:_request('get', opts, method_args))
     end
 
     function methods:min(key, opts)
@@ -1409,9 +1472,9 @@ index_metatable = function(remote)
         if opts and opts.buffer then
             error("index:min() doesn't support `buffer` argument")
         end
-        return nothing_or_data(remote:_request('min', opts, self.space.id,
-                                               self.id, box.index.GE, 0, 1,
-                                               key))
+        local method_args = {space_id=self.space.id, index_id=self.id,
+                             iterator=box.index.GE, offset=0, limit=1, key=key}
+        return nothing_or_data(remote:_request('min', opts, method_args))
     end
 
     function methods:max(key, opts)
@@ -1419,9 +1482,9 @@ index_metatable = function(remote)
         if opts and opts.buffer then
             error("index:max() doesn't support `buffer` argument")
         end
-        return nothing_or_data(remote:_request('max', opts, self.space.id,
-                                               self.id, box.index.LE, 0, 1,
-                                               key))
+        local method_args = {space_id=self.space.id, index_id=self.id,
+                             iterator=box.index.LE, offset=0, limit=1, key=key}
+        return nothing_or_data(remote:_request('max', opts, method_args))
     end
 
     function methods:count(key, opts)
@@ -1431,19 +1494,21 @@ index_metatable = function(remote)
         end
         local code = string.format('box.space.%s.index.%s:count',
                                    self.space.name, self.name)
-        return remote:_request('count', opts, code, { key, opts })
+        local method_args = {func_name=code, args={ key, opts }}
+        return remote:_request('count', opts, method_args)
     end
 
     function methods:delete(key, opts)
         check_index_arg(self, 'delete')
-        return nothing_or_data(remote:_request('delete', opts, self.space.id,
-                                               self.id, key))
+        local method_args = {space_id=self.space.id, index_id=self.id, key=key}
+        return nothing_or_data(remote:_request('delete', opts, method_args))
     end
 
     function methods:update(key, oplist, opts)
         check_index_arg(self, 'update')
-        return nothing_or_data(remote:_request('update', opts, self.space.id,
-                                               self.id, key, oplist))
+        local method_args = {space_id=self.space.id, index_id=self.id, key=key,
+                             oplist=oplist}
+        return nothing_or_data(remote:_request('update', opts, method_args))
     end
 
     return { __index = methods, __metatable = false }
diff --git a/test/box/access.result b/test/box/access.result
index 0ebc6a3..e3d4036 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -890,15 +890,24 @@ LISTEN = require('uri').parse(box.cfg.listen)
 c = (require 'net.box').connect(LISTEN.host, LISTEN.service)
 ---
 ...
-c:_request("select", nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
+args = {space_id=1, index_id=0, iterator=box.index.EQ, offset=0, limit=0xFFFFFFFF, key={}}
+---
+...
+c:_request("select", nil, args)
 ---
 - error: Space '1' does not exist
 ...
-c:_request("select", nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
+args.space_id = 65537
+---
+...
+c:_request("select", nil, args)
 ---
 - error: Space '65537' does not exist
 ...
-c:_request("select", nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
+args.space_id = 4294967295
+---
+...
+c:_request("select", nil, args)
 ---
 - error: Space '4294967295' does not exist
 ...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index ad63a10..c63152b 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -343,9 +343,12 @@ box.schema.func.drop(name)
 -- very large space id, no crash occurs.
 LISTEN = require('uri').parse(box.cfg.listen)
 c = (require 'net.box').connect(LISTEN.host, LISTEN.service)
-c:_request("select", nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
-c:_request("select", nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
-c:_request("select", nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
+args = {space_id=1, index_id=0, iterator=box.index.EQ, offset=0, limit=0xFFFFFFFF, key={}}
+c:_request("select", nil, args)
+args.space_id = 65537
+c:_request("select", nil, args)
+args.space_id = 4294967295
+c:_request("select", nil, args)
 c:close()
 
 session = box.session
diff --git a/test/box/net.box.result b/test/box/net.box.result
index 474297a..451c31d 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -25,9 +25,9 @@ test_run:cmd("setopt delimiter ';'")
 - true
 ...
 function x_select(cn, space_id, index_id, iterator, offset, limit, key, opts)
-    local ret = cn:_request('select', opts, space_id, index_id, iterator,
-                            offset, limit, key)
-    return ret
+    local args = {space_id=space_id, index_id=index_id, iterator=iterator,
+                  offset=offset, limit=limit, key=key}
+    return cn:_request('select', opts, args)
 end
 function x_fatal(cn) cn._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80') end
 test_run:cmd("setopt delimiter ''");
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index bea4300..6651b58 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -8,9 +8,9 @@ test_run:cmd("push filter ".."'\\.lua.*:[0-9]+: ' to '.lua...\"]:<line>: '")
 
 test_run:cmd("setopt delimiter ';'")
 function x_select(cn, space_id, index_id, iterator, offset, limit, key, opts)
-    local ret = cn:_request('select', opts, space_id, index_id, iterator,
-                            offset, limit, key)
-    return ret
+    local args = {space_id=space_id, index_id=index_id, iterator=iterator,
+                  offset=offset, limit=limit, key=key}
+    return cn:_request('select', opts, args)
 end
 function x_fatal(cn) cn._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80') end
 test_run:cmd("setopt delimiter ''");
-- 
2.7.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v1 2/2] netbox: define formats for tuple from netbox
  2019-06-10 10:02 [PATCH v1 0/2] netbox: define formats for tuple from netbox imeevma
  2019-06-10 10:02 ` [PATCH v1 1/2] netbox: store method_encoder args in request imeevma
@ 2019-06-10 10:02 ` imeevma
  2019-06-11  8:55   ` Vladimir Davydov
  2019-06-11  8:55 ` [PATCH v1 0/2] " Vladimir Davydov
  2 siblings, 1 reply; 12+ messages in thread
From: imeevma @ 2019-06-10 10:02 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: tarantool-patches

This patch creates tuple_formats for the tuples obtained through
the netbox.

Closes #2978
---
 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)
 {
+	(void)format_id;
 	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!");
+
+	uint32_t count = lua_objlen(L, 1);
+	if (count == 0) {
+		lua_pushinteger(L, box_tuple_format_default()->id);
+		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);
+	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);
+		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);
+	assert(format != NULL);
+	tuple_format_ref(format);
+	lua_pushinteger(L, format->id);
+	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 },
 		{ 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)
+    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
     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,
     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)
             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)
         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')
+---
+...
+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')
+---
+...
+--
 -- gh-3400: long-poll input discard must not touch event loop of
 -- a closed connection.
 --
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index 6651b58..bba4eb5 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -1433,6 +1433,26 @@ box.space.test3:drop()
 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})
+box.schema.user.grant('guest', 'read, write, execute', 'universe')
+cn = net.connect(box.cfg.listen)
+s = cn.space.named
+s:get{1}.id
+s:get{1}:tomap()
+s:insert{2,3}:tomap()
+s:replace{2,14}:tomap()
+s:update(1, {{'+', 2, 10}}):tomap()
+s:select()[1]:tomap()
+s:delete({2}):tomap()
+cn:close()
+box.space.named:drop()
+box.schema.user.revoke('guest', 'read, write, execute', 'universe')
+
+--
 -- gh-3400: long-poll input discard must not touch event loop of
 -- a closed connection.
 --
-- 
2.7.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v1 1/2] netbox: store method_encoder args in request
  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
  0 siblings, 1 reply; 12+ messages in thread
From: Vladimir Davydov @ 2019-06-11  8:15 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

On Mon, Jun 10, 2019 at 01:02:23PM +0300, imeevma@tarantool.org wrote:
> This patch changes the way arguments are passed to functions in
> the array method_encoder, and saves these arguments in REQUEST.
> This is necessary to establish a connection between netbox space
> objects and the tuples obtained through the netbox
> 
> Needed for #2978
> ---
>  src/box/lua/net_box.lua   | 179 +++++++++++++++++++++++++++++++---------------
>  test/box/access.result    |  15 +++-
>  test/box/access.test.lua  |   9 ++-
>  test/box/net.box.result   |   6 +-
>  test/box/net.box.test.lua |   6 +-
>  5 files changed, 146 insertions(+), 69 deletions(-)
> 
> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
> index 251ad40..8d42fb4 100644
> --- a/src/box/lua/net_box.lua
> +++ b/src/box/lua/net_box.lua
> @@ -25,7 +25,6 @@ local check_primary_index = box.internal.check_primary_index
>  
>  local communicate     = internal.communicate
>  local encode_auth     = internal.encode_auth
> -local encode_select   = internal.encode_select
>  local decode_greeting = internal.decode_greeting
>  
>  local TIMEOUT_INFINITY = 500 * 365 * 86400
> @@ -84,22 +83,70 @@ local function decode_push(raw_data)
>      return response[IPROTO_DATA_KEY][1], raw_end
>  end
>  
> +local function encode_call(send_buf, id, method_args)
> +    return internal.encode_call(send_buf, id, method_args.func_name,
> +                                method_args.args)
> +end
> +local function encode_call_16(send_buf, id, method_args)
> +    return internal.encode_call_16(send_buf, id, method_args.func_name,
> +                                   method_args.args)
> +end
> +local function encode_ping(send_buf, id, ...)
> +    return internal.encode_ping(send_buf, id, ...)
> +end
> +local function encode_eval(send_buf, id, method_args)
> +    return internal.encode_eval(send_buf, id, method_args.code,
> +                                method_args.args)
> +end
> +local function encode_insert(send_buf, id, method_args)
> +    return internal.encode_insert(send_buf, id, method_args.space_id,
> +                                  method_args.tuple)
> +end
> +local function encode_replace(send_buf, id, method_args)
> +    return internal.encode_replace(send_buf, id, method_args.space_id,
> +                                   method_args.tuple)
> +end
> +local function encode_delete(send_buf, id, method_args)
> +    return internal.encode_delete(send_buf, id, method_args.space_id,
> +                                  method_args.index_id, method_args.key)
> +end
> +local function encode_update(send_buf, id, method_args)
> +    return internal.encode_update(send_buf, id, method_args.space_id,
> +                                  method_args.index_id, method_args.key,
> +                                  method_args.oplist)
> +end
> +local function encode_upsert(send_buf, id, method_args)
> +    return internal.encode_upsert(send_buf, id, method_args.space_id,
> +                                  method_args.key, method_args.oplist)
> +end
> +local function encode_select(send_buf, id, method_args)
> +    return internal.encode_select(send_buf, id, method_args.space_id,
> +                                  method_args.index_id, method_args.iterator,
> +                                  method_args.offset, method_args.limit,
> +                                  method_args.key)
> +end
> +local function encode_execute(send_buf, id, method_args)
> +    return internal.encode_execute(send_buf, id, method_args.query,
> +                                   method_args.parameters, method_args.sql_opts)
> +end

Why not call it simply 'args'?

> @@ -1150,14 +1201,16 @@ end
>  -- @deprecated since 1.7.4
>  function remote_methods:eval_16(code, ...)
>      check_remote_arg(self, 'eval')
> -    return unpack((self:_request('eval', nil, code, {...})))
> +    local method_args = {code=code, args={...}}
> +    return unpack((self:_request('eval', nil, method_args)))
>  end
>  
>  function remote_methods:eval(code, args, opts)
>      check_remote_arg(self, 'eval')
>      check_eval_args(args)
>      args = args or {}
> -    local res = self:_request('eval', opts, code, args)
> +    local method_args = {code=code, args=args}

Coding style: we add a space before and after '=':

	local method_args = {code = code, args = args}

Please fix here and everywhere else.

Other than that this patch looks okay to me.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v1 2/2] netbox: define formats for tuple from netbox
  2019-06-10 10:02 ` [PATCH v1 2/2] netbox: define formats for tuple from netbox imeevma
@ 2019-06-11  8:55   ` Vladimir Davydov
  2019-06-14 12:29     ` Mergen Imeev
  0 siblings, 1 reply; 12+ messages in thread
From: Vladimir Davydov @ 2019-06-11  8:55 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

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')
> +---
> +...
> +--

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v1 0/2] netbox: define formats for tuple from netbox
  2019-06-10 10:02 [PATCH v1 0/2] netbox: define formats for tuple from netbox imeevma
  2019-06-10 10:02 ` [PATCH v1 1/2] netbox: store method_encoder args in request imeevma
  2019-06-10 10:02 ` [PATCH v1 2/2] netbox: define formats for tuple from netbox imeevma
@ 2019-06-11  8:55 ` Vladimir Davydov
  2019-06-14 12:32   ` Mergen Imeev
  2 siblings, 1 reply; 12+ messages in thread
From: Vladimir Davydov @ 2019-06-11  8:55 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

On Mon, Jun 10, 2019 at 01:02:21PM +0300, imeevma@tarantool.org wrote:
> This patch defines field names for the tuples obtained through
> the netbox.

Links to the branch and the ticket are missing.

> 
> Mergen Imeev (2):
>   netbox: store method_encoder args in request
>   netbox: define formats for tuple from netbox
> 
>  src/box/lua/net_box.c     |  87 ++++++++++++++++-
>  src/box/lua/net_box.lua   | 232 +++++++++++++++++++++++++++++++++-------------
>  test/box/access.result    |  15 ++-
>  test/box/access.test.lua  |   9 +-
>  test/box/net.box.result   |  83 ++++++++++++++++-
>  test/box/net.box.test.lua |  26 +++++-
>  6 files changed, 369 insertions(+), 83 deletions(-)

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v1 1/2] netbox: store method_encoder args in request
  2019-06-11  8:15   ` Vladimir Davydov
@ 2019-06-14 11:50     ` Mergen Imeev
  2019-06-18  8:38       ` Vladimir Davydov
  0 siblings, 1 reply; 12+ messages in thread
From: Mergen Imeev @ 2019-06-14 11:50 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

On Tue, Jun 11, 2019 at 11:15:06AM +0300, Vladimir Davydov wrote:
> On Mon, Jun 10, 2019 at 01:02:23PM +0300, imeevma@tarantool.org wrote:
> > This patch changes the way arguments are passed to functions in
> > the array method_encoder, and saves these arguments in REQUEST.
> > This is necessary to establish a connection between netbox space
> > objects and the tuples obtained through the netbox
> > 
> > Needed for #2978
> > ---
> >  src/box/lua/net_box.lua   | 179 +++++++++++++++++++++++++++++++---------------
> >  test/box/access.result    |  15 +++-
> >  test/box/access.test.lua  |   9 ++-
> >  test/box/net.box.result   |   6 +-
> >  test/box/net.box.test.lua |   6 +-
> >  5 files changed, 146 insertions(+), 69 deletions(-)
> > 
> > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
> > index 251ad40..8d42fb4 100644
> > --- a/src/box/lua/net_box.lua
> > +++ b/src/box/lua/net_box.lua
> > @@ -25,7 +25,6 @@ local check_primary_index = box.internal.check_primary_index
> >  
> >  local communicate     = internal.communicate
> >  local encode_auth     = internal.encode_auth
> > -local encode_select   = internal.encode_select
> >  local decode_greeting = internal.decode_greeting
> >  
> >  local TIMEOUT_INFINITY = 500 * 365 * 86400
> > @@ -84,22 +83,70 @@ local function decode_push(raw_data)
> >      return response[IPROTO_DATA_KEY][1], raw_end
> >  end
> >  
> > +local function encode_call(send_buf, id, method_args)
> > +    return internal.encode_call(send_buf, id, method_args.func_name,
> > +                                method_args.args)
> > +end
> > +local function encode_call_16(send_buf, id, method_args)
> > +    return internal.encode_call_16(send_buf, id, method_args.func_name,
> > +                                   method_args.args)
> > +end
> > +local function encode_ping(send_buf, id, ...)
> > +    return internal.encode_ping(send_buf, id, ...)
> > +end
> > +local function encode_eval(send_buf, id, method_args)
> > +    return internal.encode_eval(send_buf, id, method_args.code,
> > +                                method_args.args)
> > +end
> > +local function encode_insert(send_buf, id, method_args)
> > +    return internal.encode_insert(send_buf, id, method_args.space_id,
> > +                                  method_args.tuple)
> > +end
> > +local function encode_replace(send_buf, id, method_args)
> > +    return internal.encode_replace(send_buf, id, method_args.space_id,
> > +                                   method_args.tuple)
> > +end
> > +local function encode_delete(send_buf, id, method_args)
> > +    return internal.encode_delete(send_buf, id, method_args.space_id,
> > +                                  method_args.index_id, method_args.key)
> > +end
> > +local function encode_update(send_buf, id, method_args)
> > +    return internal.encode_update(send_buf, id, method_args.space_id,
> > +                                  method_args.index_id, method_args.key,
> > +                                  method_args.oplist)
> > +end
> > +local function encode_upsert(send_buf, id, method_args)
> > +    return internal.encode_upsert(send_buf, id, method_args.space_id,
> > +                                  method_args.key, method_args.oplist)
> > +end
> > +local function encode_select(send_buf, id, method_args)
> > +    return internal.encode_select(send_buf, id, method_args.space_id,
> > +                                  method_args.index_id, method_args.iterator,
> > +                                  method_args.offset, method_args.limit,
> > +                                  method_args.key)
> > +end
> > +local function encode_execute(send_buf, id, method_args)
> > +    return internal.encode_execute(send_buf, id, method_args.query,
> > +                                   method_args.parameters, method_args.sql_opts)
> > +end
> 
> Why not call it simply 'args'?
> 
Fixed. I used 'method_args' since 'args' was used in call
and eval.

> > @@ -1150,14 +1201,16 @@ end
> >  -- @deprecated since 1.7.4
> >  function remote_methods:eval_16(code, ...)
> >      check_remote_arg(self, 'eval')
> > -    return unpack((self:_request('eval', nil, code, {...})))
> > +    local method_args = {code=code, args={...}}
> > +    return unpack((self:_request('eval', nil, method_args)))
> >  end
> >  
> >  function remote_methods:eval(code, args, opts)
> >      check_remote_arg(self, 'eval')
> >      check_eval_args(args)
> >      args = args or {}
> > -    local res = self:_request('eval', opts, code, args)
> > +    local method_args = {code=code, args=args}
> 
> Coding style: we add a space before and after '=':
> 
> 	local method_args = {code = code, args = args}
> 
> Please fix here and everywhere else.
> 
> Other than that this patch looks okay to me.

Fixed.


New patch:

From 8be1560eb5ed57d397c5f2da97112b448505080c Mon Sep 17 00:00:00 2001
Date: Wed, 5 Jun 2019 13:31:07 +0300
Subject: [PATCH] netbox: store method_encoder args in request

This patch changes the way arguments are passed to functions in
the array method_encoder, and saves these arguments in REQUEST.
This is necessary to establish a connection between netbox space
objects and the tuples obtained through the netbox

Needed for #2978

diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 251ad40..d9838f8 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -25,7 +25,6 @@ local check_primary_index = box.internal.check_primary_index
 
 local communicate     = internal.communicate
 local encode_auth     = internal.encode_auth
-local encode_select   = internal.encode_select
 local decode_greeting = internal.decode_greeting
 
 local TIMEOUT_INFINITY = 500 * 365 * 86400
@@ -84,22 +83,63 @@ local function decode_push(raw_data)
     return response[IPROTO_DATA_KEY][1], raw_end
 end
 
+local function encode_call(send_buf, id, args)
+    return internal.encode_call(send_buf, id, args.func_name, args.args)
+end
+local function encode_call_16(send_buf, id, args)
+    return internal.encode_call_16(send_buf, id, args.func_name, args.args)
+end
+local function encode_ping(send_buf, id, ...)
+    return internal.encode_ping(send_buf, id, ...)
+end
+local function encode_eval(send_buf, id, args)
+    return internal.encode_eval(send_buf, id, args.code, args.args)
+end
+local function encode_insert(send_buf, id, args)
+    return internal.encode_insert(send_buf, id, args.space_id, args.tuple)
+end
+local function encode_replace(send_buf, id, args)
+    return internal.encode_replace(send_buf, id, args.space_id, args.tuple)
+end
+local function encode_delete(send_buf, id, args)
+    return internal.encode_delete(send_buf, id, args.space_id, args.index_id,
+                                  args.key)
+end
+local function encode_update(send_buf, id, args)
+    return internal.encode_update(send_buf, id, args.space_id, args.index_id,
+                                  args.key, args.oplist)
+end
+local function encode_upsert(send_buf, id, args)
+    return internal.encode_upsert(send_buf, id, args.space_id, args.key,
+                                  args.oplist)
+end
+local function encode_select(send_buf, id, args)
+    return internal.encode_select(send_buf, id, args.space_id, args.index_id,
+                                  args.iterator, args.offset, args.limit,
+                                  args.key)
+end
+local function encode_execute(send_buf, id, args)
+    return internal.encode_execute(send_buf, id, args.query, args.parameters,
+                                   args.sql_opts)
+end
+
+
 local method_encoder = {
-    ping    = internal.encode_ping,
-    call_16 = internal.encode_call_16,
-    call_17 = internal.encode_call,
-    eval    = internal.encode_eval,
-    insert  = internal.encode_insert,
-    replace = internal.encode_replace,
-    delete  = internal.encode_delete,
-    update  = internal.encode_update,
-    upsert  = internal.encode_upsert,
-    select  = internal.encode_select,
-    execute = internal.encode_execute,
-    get     = internal.encode_select,
-    min     = internal.encode_select,
-    max     = internal.encode_select,
-    count   = internal.encode_call,
+    ping    = encode_ping,
+    call_16 = encode_call_16,
+    call_17 = encode_call,
+    eval    = encode_eval,
+    insert  = encode_insert,
+    replace = encode_replace,
+    delete  = encode_delete,
+    update  = encode_update,
+    upsert  = encode_upsert,
+    select  = encode_select,
+    execute = encode_execute,
+    get     = encode_select,
+    min     = encode_select,
+    max     = encode_select,
+    count   = encode_call,
     -- inject raw data into connection, used by console and tests
     inject = function(buf, id, bytes)
         local ptr = buf:reserve(#bytes)
@@ -486,7 +526,7 @@ local function create_transport(host, port, user, password, callback,
     -- @retval not nil Future object.
     --
     local function perform_async_request(buffer, skip_header, method, on_push,
-                                         on_push_ctx, ...)
+                                         on_push_ctx, args)
         if state ~= 'active' and state ~= 'fetch_schema' then
             return nil, box.error.new({code = last_errno or E_NO_CONNECTION,
                                        reason = last_error})
@@ -497,12 +537,12 @@ local function create_transport(host, port, user, password, callback,
             worker_fiber:wakeup()
         end
         local id = next_request_id
-        method_encoder[method](send_buf, id, ...)
+        method_encoder[method](send_buf, id, args)
         next_request_id = next_id(id)
-        -- Request in most cases has maximum 9 members:
+        -- Request in most cases has maximum 10 members:
         -- method, buffer, skip_header, id, cond, errno, response,
-        -- on_push, on_push_ctx.
-        local request = setmetatable(table_new(0, 9), request_mt)
+        -- on_push, on_push_ctx and args.
+        local request = setmetatable(table_new(0, 10), request_mt)
         request.method = method
         request.buffer = buffer
         request.skip_header = skip_header
@@ -511,6 +551,7 @@ local function create_transport(host, port, user, password, callback,
         requests[id] = request
         request.on_push = on_push
         request.on_push_ctx = on_push_ctx
+        request.args = args
         return request
     end
 
@@ -520,10 +561,10 @@ local function create_transport(host, port, user, password, callback,
     -- @retval not nil Response object.
     --
     local function perform_request(timeout, buffer, skip_header, method,
-                                   on_push, on_push_ctx, ...)
+                                   on_push, on_push_ctx, args)
         local request, err =
             perform_async_request(buffer, skip_header, method, on_push,
-                                  on_push_ctx, ...)
+                                  on_push_ctx, args)
         if not request then
             return nil, err
         end
@@ -739,13 +780,14 @@ local function create_transport(host, port, user, password, callback,
         local select3_id = new_request_id()
         local response = {}
         -- fetch everything from space _vspace, 2 = ITER_ALL
-        encode_select(send_buf, select1_id, VSPACE_ID, 0, 2, 0, 0xFFFFFFFF, nil)
+        internal.encode_select(send_buf, select1_id, VSPACE_ID, 0, 2, 0,
+                               0xFFFFFFFF, nil)
         -- fetch everything from space _vindex, 2 = ITER_ALL
-        encode_select(send_buf, select2_id, VINDEX_ID, 0, 2, 0, 0xFFFFFFFF, nil)
+        internal.encode_select(send_buf, select2_id, VINDEX_ID, 0, 2, 0,
+                               0xFFFFFFFF, nil)
         -- fetch everything from space _vcollation, 2 = ITER_ALL
-        encode_select(send_buf, select3_id, VCOLLATION_ID, 0, 2, 0, 0xFFFFFFFF,
-                      nil)
-
+        internal.encode_select(send_buf, select3_id, VCOLLATION_ID, 0, 2, 0,
+                               0xFFFFFFFF, nil)
         schema_version = nil -- any schema_version will do provided that
                              -- it is consistent across responses
         repeat
@@ -1064,7 +1106,7 @@ function remote_methods:wait_connected(timeout)
     return self._transport.wait_state('active', timeout)
 end
 
-function remote_methods:_request(method, opts, ...)
+function remote_methods:_request(method, opts, args)
     local transport = self._transport
     local on_push, on_push_ctx, buffer, skip_header, deadline
     -- Extract options, set defaults, check if the request is
@@ -1078,7 +1120,7 @@ function remote_methods:_request(method, opts, ...)
             end
             local res, err =
                 transport.perform_async_request(buffer, skip_header, method,
-                                                table.insert, {}, ...)
+                                                table.insert, {}, args)
             if err then
                 box.error(err)
             end
@@ -1106,7 +1148,7 @@ function remote_methods:_request(method, opts, ...)
     end
     local res, err = transport.perform_request(timeout, buffer, skip_header,
                                                method, on_push, on_push_ctx,
-                                               ...)
+                                               args)
     if err then
         box.error(err)
     end
@@ -1133,14 +1175,16 @@ end
 -- @deprecated since 1.7.4
 function remote_methods:call_16(func_name, ...)
     check_remote_arg(self, 'call')
-    return (self:_request('call_16', nil, tostring(func_name), {...}))
+    local args = {func_name = tostring(func_name), args = {...}}
+    return (self:_request('call_16', nil, args))
 end
 
-function remote_methods:call(func_name, args, opts)
+function remote_methods:call(func_name, call_args, opts)
     check_remote_arg(self, 'call')
-    check_call_args(args)
-    args = args or {}
-    local res = self:_request('call_17', opts, tostring(func_name), args)
+    check_call_args(call_args)
+    call_args = call_args or {}
+    local args = {func_name = tostring(func_name), args = call_args}
+    local res = self:_request('call_17', opts, args)
     if type(res) ~= 'table' or opts and opts.is_async then
         return res
     end
@@ -1150,14 +1194,16 @@ end
 -- @deprecated since 1.7.4
 function remote_methods:eval_16(code, ...)
     check_remote_arg(self, 'eval')
-    return unpack((self:_request('eval', nil, code, {...})))
+    local args = {code = code, args = {...}}
+    return unpack((self:_request('eval', nil, args)))
 end
 
-function remote_methods:eval(code, args, opts)
+function remote_methods:eval(code, eval_args, opts)
     check_remote_arg(self, 'eval')
-    check_eval_args(args)
-    args = args or {}
-    local res = self:_request('eval', opts, code, args)
+    check_eval_args(eval_args)
+    eval_args = eval_args or {}
+    local args = {code = code, args = eval_args}
+    local res = self:_request('eval', opts, args)
     if type(res) ~= 'table' or opts and opts.is_async then
         return res
     end
@@ -1169,8 +1215,9 @@ function remote_methods:execute(query, parameters, sql_opts, netbox_opts)
     if sql_opts ~= nil then
         box.error(box.error.UNSUPPORTED, "execute", "options")
     end
-    return self:_request('execute', netbox_opts, query, parameters or {},
-                         sql_opts or {})
+    local args = {query =query, parameters = parameters or {},
+                  sql_opts = sql_opts or {}}
+    return self:_request('execute', netbox_opts, args)
 end
 
 function remote_methods:wait_state(state, timeout)
@@ -1314,7 +1361,8 @@ function console_methods:eval(line, timeout)
     end
     if self.protocol == 'Binary' then
         local loader = 'return require("console").eval(...)'
-        res, err = pr(timeout, nil, false, 'eval', nil, nil, loader, {line})
+        local args = {code = loader, args = {line}}
+        res, err = pr(timeout, nil, false, 'eval', nil, nil, args)
     else
         assert(self.protocol == 'Lua console')
         res, err = pr(timeout, nil, false, 'inject', nil, nil, line..'$EOF$\n')
@@ -1336,12 +1384,14 @@ space_metatable = function(remote)
 
     function methods:insert(tuple, opts)
         check_space_arg(self, 'insert')
-        return remote:_request('insert', opts, self.id, tuple)
+        local args = {space_id = self.id, tuple = tuple}
+        return remote:_request('insert', opts, args)
     end
 
     function methods:replace(tuple, opts)
         check_space_arg(self, 'replace')
-        return remote:_request('replace', opts, self.id, tuple)
+        local args = {space_id = self.id, tuple = tuple}
+        return remote:_request('replace', opts, args)
     end
 
     function methods:select(key, opts)
@@ -1361,8 +1411,8 @@ space_metatable = function(remote)
 
     function methods:upsert(key, oplist, opts)
         check_space_arg(self, 'upsert')
-        return nothing_or_data(remote:_request('upsert', opts, self.id, key,
-                                               oplist))
+        local args = {space_id = self.id, key = key, oplist = oplist}
+        return nothing_or_data(remote:_request('upsert', opts, args))
     end
 
     function methods:get(key, opts)
@@ -1391,8 +1441,10 @@ index_metatable = function(remote)
         local iterator = check_iterator_type(opts, key_is_nil)
         local offset = tonumber(opts and opts.offset) or 0
         local limit = tonumber(opts and opts.limit) or 0xFFFFFFFF
-        return (remote:_request('select', opts, self.space.id, self.id,
-                                iterator, offset, limit, key))
+        local args = {space_id = self.space.id, index_id = self.id,
+                      iterator = iterator, offset = offset, limit = limit,
+                      key = key}
+        return (remote:_request('select', opts, args))
     end
 
     function methods:get(key, opts)
@@ -1400,8 +1452,9 @@ index_metatable = function(remote)
         if opts and opts.buffer then
             error("index:get() doesn't support `buffer` argument")
         end
-        return nothing_or_data(remote:_request('get', opts, self.space.id,
-                               self.id, box.index.EQ, 0, 2, key))
+        local args = {space_id = self.space.id, index_id = self.id,
+                      iterator = box.index.EQ, offset = 0, limit = 2, key = key}
+        return nothing_or_data(remote:_request('get', opts, args))
     end
 
     function methods:min(key, opts)
@@ -1409,9 +1462,9 @@ index_metatable = function(remote)
         if opts and opts.buffer then
             error("index:min() doesn't support `buffer` argument")
         end
-        return nothing_or_data(remote:_request('min', opts, self.space.id,
-                                               self.id, box.index.GE, 0, 1,
-                                               key))
+        local args = {space_id = self.space.id, index_id = self.id,
+                      iterator = box.index.GE, offset = 0, limit = 1, key = key}
+        return nothing_or_data(remote:_request('min', opts, args))
     end
 
     function methods:max(key, opts)
@@ -1419,9 +1472,9 @@ index_metatable = function(remote)
         if opts and opts.buffer then
             error("index:max() doesn't support `buffer` argument")
         end
-        return nothing_or_data(remote:_request('max', opts, self.space.id,
-                                               self.id, box.index.LE, 0, 1,
-                                               key))
+        local args = {space_id = self.space.id, index_id = self.id,
+                      iterator = box.index.LE, offset = 0, limit = 1, key = key}
+        return nothing_or_data(remote:_request('max', opts, args))
     end
 
     function methods:count(key, opts)
@@ -1431,19 +1484,21 @@ index_metatable = function(remote)
         end
         local code = string.format('box.space.%s.index.%s:count',
                                    self.space.name, self.name)
-        return remote:_request('count', opts, code, { key, opts })
+        local args = {func_name = code, args = { key, opts }}
+        return remote:_request('count', opts, args)
     end
 
     function methods:delete(key, opts)
         check_index_arg(self, 'delete')
-        return nothing_or_data(remote:_request('delete', opts, self.space.id,
-                                               self.id, key))
+        local args = {space_id = self.space.id, index_id = self.id, key = key}
+        return nothing_or_data(remote:_request('delete', opts, args))
     end
 
     function methods:update(key, oplist, opts)
         check_index_arg(self, 'update')
-        return nothing_or_data(remote:_request('update', opts, self.space.id,
-                                               self.id, key, oplist))
+        local args = {space_id = self.space.id, index_id = self.id, key = key,
+                      oplist = oplist}
+        return nothing_or_data(remote:_request('update', opts, args))
     end
 
     return { __index = methods, __metatable = false }
diff --git a/test/box/access.result b/test/box/access.result
index 0ebc6a3..e3d4036 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -890,15 +890,24 @@ LISTEN = require('uri').parse(box.cfg.listen)
 c = (require 'net.box').connect(LISTEN.host, LISTEN.service)
 ---
 ...
-c:_request("select", nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
+args = {space_id=1, index_id=0, iterator=box.index.EQ, offset=0, limit=0xFFFFFFFF, key={}}
+---
+...
+c:_request("select", nil, args)
 ---
 - error: Space '1' does not exist
 ...
-c:_request("select", nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
+args.space_id = 65537
+---
+...
+c:_request("select", nil, args)
 ---
 - error: Space '65537' does not exist
 ...
-c:_request("select", nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
+args.space_id = 4294967295
+---
+...
+c:_request("select", nil, args)
 ---
 - error: Space '4294967295' does not exist
 ...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index ad63a10..c63152b 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -343,9 +343,12 @@ box.schema.func.drop(name)
 -- very large space id, no crash occurs.
 LISTEN = require('uri').parse(box.cfg.listen)
 c = (require 'net.box').connect(LISTEN.host, LISTEN.service)
-c:_request("select", nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
-c:_request("select", nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
-c:_request("select", nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
+args = {space_id=1, index_id=0, iterator=box.index.EQ, offset=0, limit=0xFFFFFFFF, key={}}
+c:_request("select", nil, args)
+args.space_id = 65537
+c:_request("select", nil, args)
+args.space_id = 4294967295
+c:_request("select", nil, args)
 c:close()
 
 session = box.session
diff --git a/test/box/net.box.result b/test/box/net.box.result
index 474297a..e87a60b 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -25,9 +25,9 @@ test_run:cmd("setopt delimiter ';'")
 - true
 ...
 function x_select(cn, space_id, index_id, iterator, offset, limit, key, opts)
-    local ret = cn:_request('select', opts, space_id, index_id, iterator,
-                            offset, limit, key)
-    return ret
+    local args = {space_id = space_id, index_id = index_id, iterator = iterator,
+                  offset = offset, limit = limit, key = key}
+    return cn:_request('select', opts, args)
 end
 function x_fatal(cn) cn._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80') end
 test_run:cmd("setopt delimiter ''");
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index bea4300..6e58da8 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -8,9 +8,9 @@ test_run:cmd("push filter ".."'\\.lua.*:[0-9]+: ' to '.lua...\"]:<line>: '")
 
 test_run:cmd("setopt delimiter ';'")
 function x_select(cn, space_id, index_id, iterator, offset, limit, key, opts)
-    local ret = cn:_request('select', opts, space_id, index_id, iterator,
-                            offset, limit, key)
-    return ret
+    local args = {space_id = space_id, index_id = index_id, iterator = iterator,
+                  offset = offset, limit = limit, key = key}
+    return cn:_request('select', opts, args)
 end
 function x_fatal(cn) cn._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80') end
 test_run:cmd("setopt delimiter ''");

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v1 2/2] netbox: define formats for tuple from netbox
  2019-06-11  8:55   ` Vladimir Davydov
@ 2019-06-14 12:29     ` Mergen Imeev
  2019-06-18  8:55       ` Vladimir Davydov
  2019-06-18  9:00       ` Vladimir Davydov
  0 siblings, 2 replies; 12+ messages in thread
From: Mergen Imeev @ 2019-06-14 12:29 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

Thank you for review! There will be two new patches below.


On Tue, Jun 11, 2019 at 11:55:11AM +0300, Vladimir Davydov wrote:
> 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.
> 
Done:

@TarantoolBot document
Title: Field names for tuples received from net.box

It is possible now to access by field name for tuples received
from net.box. For example:

box.cfg{listen = 3302}
box.schema.user.grant('guest','read, write, execute', 'space')
box.schema.user.grant('guest', 'create', 'space')

box.schema.create_space("named", {format = {{name = "id"}}})
box.space.named:create_index('id', {parts = {{1, 'unsigned'}}})
box.space.named:insert({1})
require('net.box').connect('localhost', 3302).space.named:get(1).id

Result:

tarantool> require('net.box').connect('localhost', 3302).space.named:get(1).id
---
- 1
...

> >  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.
> 
Done.

> >  {
> > +	(void)format_id;
> 
> This is clearly useless.
> 
Fixed.

> > +	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.
> 
Fixed.

> > +
> > +	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.
> 
Fixed.

> > +	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.
> 
Fixed.

> > +
> > +		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.
> 
Fixed.

> > +		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.
You are right.

> What happens if a field is nullable?
> 
It works fine, I think:

box.cfg{listen = 3302}
box.schema.user.grant('guest','read, write, execute', 'space')
box.schema.user.grant('guest', 'create', 'space')

box.schema.create_space("named", {format = {{name = "id"}, {name = "nf", is_nullable = true}}})
box.space.named:create_index('id', {parts = {{1, 'unsigned'}}})
box.space.named:insert({1})
box.space.named:insert({2,3})
box.space.named:get(1):tomap()
box.space.named:get(2):tomap()
require('net.box').connect('localhost', 3302).space.named:get(1):tomap()
require('net.box').connect('localhost', 3302).space.named:get(2):tomap()

tarantool> box.space.named:get(1):tomap()
---
- 1: 1
  id: 1
...

tarantool> box.space.named:get(2):tomap()
---
- 1: 2
  2: 3
  nf: 3
  id: 2
...

tarantool> require('net.box').connect('localhost', 3302).space.named:get(1):tomap()
---
- 1: 1
  id: 1
...

tarantool> require('net.box').connect('localhost', 3302).space.named:get(2):tomap()
---
- 1: 2
  2: 3
  nf: 3
  id: 2
...

> > +	assert(format != NULL);
> 
> Strictly speaking tuple_format_new() may fail with OOM.
> Please handle it.
>
Fixed.
 
> > +	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.
> 
Fixed.

> > +	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).
> 
Done. Patch below.

> >  		{ 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.
> 
Fixed.

> > +    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?
> 
You are right, fixed.

> >      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).
> 
Fixed.

> >      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.
> 
Fixed.

> >              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?
> 
Fixed since __gc is set now.

> > +-- 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.
> 
Fixed.

> 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.
> 
Done.


First new patch:

From 63075deb8411643d4af0cf27be2c024b5a20222c Mon Sep 17 00:00:00 2001
Date: Tue, 11 Jun 2019 15:21:39 +0300
Subject: [PATCH] lua: internal function to parse space format

This patch defines a new function that parses space format and
returns it to Lua as cdata. For this cdata destructor is included.

Needed for #2978

diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc
index 8de7401..e62e2ba 100644
--- a/src/box/lua/misc.cc
+++ b/src/box/lua/misc.cc
@@ -37,9 +37,13 @@
 
 #include "box/box.h"
 #include "box/port.h"
+#include "box/tuple.h"
+#include "box/tuple_format.h"
 #include "box/lua/tuple.h"
 #include "mpstream.h"
 
+uint32_t CTID_STRUCT_TUPLE_FORMAT_PTR;
+
 /** {{{ Miscellaneous utils **/
 
 char *
@@ -116,14 +120,117 @@ lbox_select(lua_State *L)
 
 /* }}} */
 
+static int
+lbox_tuple_format_gc(struct lua_State *L)
+{
+	uint32_t ctypeid;
+	struct tuple_format *format =
+		*(struct tuple_format **)luaL_checkcdata(L, 1, &ctypeid);
+	if (ctypeid != CTID_STRUCT_TUPLE_FORMAT_PTR) {
+		luaL_error(L, "Invalid argument: 'struct tuple_format *' "\
+			   "expected, got %s)",
+			   lua_typename(L, lua_type(L, 1)));
+	}
+	box_tuple_format_unref(format);
+	return 0;
+}
+
+static int
+lbox_push_tuple_format(struct lua_State *L, struct tuple_format *format)
+{
+	struct tuple_format **ptr = (struct tuple_format **)
+		luaL_pushcdata(L, CTID_STRUCT_TUPLE_FORMAT_PTR);
+	*ptr = format;
+	box_tuple_format_ref(format);
+	lua_pushcfunction(L, lbox_tuple_format_gc);
+	luaL_setcdatagc(L, -2);
+	return 1;
+}
+
+static int
+lbox_format_new(struct lua_State *L)
+{
+	assert(CTID_STRUCT_TUPLE_FORMAT_PTR != 0);
+	if (lua_gettop(L) != 1 || ! lua_istable(L, 1))
+		return luaL_error(L, "Usage: box.internal.format_new(format)");
+	uint32_t count = lua_objlen(L, 1);
+	if (count == 0)
+		return lbox_push_tuple_format(L, tuple_format_runtime);
+	size_t size = count * sizeof(struct field_def);
+	struct region *region = &fiber()->gc;
+	size_t region_svp = region_used(region);
+	struct field_def *fields =
+		(struct field_def *)region_alloc(region, size);
+	if (fields == NULL) {
+		diag_set(OutOfMemory, size, "region_alloc", "fields");
+		return luaT_error(L);
+	}
+	for (uint32_t i = 0; i < count; ++i) {
+		size_t len;
+
+		fields[i] = field_def_default;
+
+		lua_pushinteger(L, i + 1);
+		lua_gettable(L, 1);
+
+		lua_pushstring(L, "type");
+		lua_gettable(L, -2);
+		if (! lua_isnil(L, -1)) {
+			const char *type_name = lua_tolstring(L, -1, &len);
+			fields[i].type = field_type_by_name(type_name, len);
+			if (fields[i].type == field_type_MAX) {
+				const char *err =
+					"field %d has unknown field type";
+				return luaL_error(L, tt_sprintf(err, i + 1));
+			}
+		}
+		lua_pop(L, 1);
+
+		lua_pushstring(L, "name");
+		lua_gettable(L, -2);
+		if (lua_isnil(L, -1)) {
+			return luaL_error(L, tt_sprintf("field %d name is not "\
+							"specified", i + 1));
+		}
+		const char *name = lua_tolstring(L, -1, &len);
+		fields[i].name = (char *)region_alloc(region, len + 1);
+		if (fields == NULL) {
+			diag_set(OutOfMemory, size, "region_alloc",
+				 "fields[i].name");
+			return luaT_error(L);
+		}
+		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);
+	region_truncate(region, region_svp);
+	if (dict == NULL)
+		return luaT_error(L);
+	struct tuple_format *format =
+		tuple_format_new(&tuple_format_runtime->vtab, NULL, NULL, 0,
+				 NULL, 0, 0, dict, false, false);
+	if (format == NULL)
+		return luaT_error(L);
+	return lbox_push_tuple_format(L, format);
+}
+
 void
 box_lua_misc_init(struct lua_State *L)
 {
 	static const struct luaL_Reg boxlib_internal[] = {
 		{"select", lbox_select},
+		{"format_new", lbox_format_new},
 		{NULL, NULL}
 	};
 
 	luaL_register(L, "box.internal", boxlib_internal);
 	lua_pop(L, 1);
+
+	int rc = luaL_cdef(L, "struct tuple_format;");
+	assert(rc == 0);
+	(void) rc;
+	CTID_STRUCT_TUPLE_FORMAT_PTR = luaL_ctypeid(L, "struct tuple_format *");
+	assert(CTID_STRUCT_TUPLE_FORMAT_PTR != 0);
 }
diff --git a/test/box/misc.result b/test/box/misc.result
index 43b5a4a..ce39bbb 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -1271,3 +1271,51 @@ box.cfg{too_long_threshold = too_long_threshold}
 s:drop()
 ---
 ...
+--
+-- gh-2978: Function to parse space format.
+--
+format = {}
+---
+...
+format[1] = {}
+---
+...
+format[1].type = 'unsigned'
+---
+...
+box.internal.format_new(format)
+---
+- error: field 1 name is not specified
+...
+format[1].name = 'aaa'
+---
+...
+format[2] = {}
+---
+...
+format[2].name = 'aaa'
+---
+...
+format[2].type = 'any'
+---
+...
+box.internal.format_new(format)
+---
+- error: Space field 'aaa' is duplicate
+...
+format[2].name = 'bbb'
+---
+...
+format[3] = {}
+---
+...
+format[3].name = 'ccc'
+---
+...
+format[3].type = 'something'
+---
+...
+box.internal.format_new(format)
+---
+- error: field 3 has unknown field type
+...
diff --git a/test/box/misc.test.lua b/test/box/misc.test.lua
index 75d937e..2d13b99 100644
--- a/test/box/misc.test.lua
+++ b/test/box/misc.test.lua
@@ -353,3 +353,23 @@ lsn == expected_lsn
 box.cfg{too_long_threshold = too_long_threshold}
 s:drop()
 
+
+--
+-- gh-2978: Function to parse space format.
+--
+format = {}
+format[1] = {}
+format[1].type = 'unsigned'
+box.internal.format_new(format)
+
+format[1].name = 'aaa'
+format[2] = {}
+format[2].name = 'aaa'
+format[2].type = 'any'
+box.internal.format_new(format)
+
+format[2].name = 'bbb'
+format[3] = {}
+format[3].name = 'ccc'
+format[3].type = 'something'
+box.internal.format_new(format)




Second new patch:

From f9e959e3f0f38e8f6b8f1294ba29c28d41f30968 Mon Sep 17 00:00:00 2001
Date: Tue, 11 Jun 2019 16:36:39 +0300
Subject: [PATCH] netbox: define formats for tuple from netbox

This patch creates tuple_formats for the tuples obtained through
the netbox.

Closes #2978

@TarantoolBot document
Title: Field names for tuples received from net.box

It is possible now to access by field name for tuples received
from net.box. For example:

box.cfg{listen = 3302}
box.schema.user.grant('guest','read, write, execute', 'space')
box.schema.user.grant('guest', 'create', 'space')

box.schema.create_space("named", {format = {{name = "id"}}})
box.space.named:create_index('id', {parts = {{1, 'unsigned'}}})
box.space.named:insert({1})
require('net.box').connect('localhost', 3302).space.named:get(1).id

Result:

tarantool> require('net.box').connect('localhost', 3302).space.named:get(1).id
---
- 1
...

diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index 7484a86..946d397 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -590,12 +590,11 @@ 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,
+		   struct tuple_format *format)
 {
 	uint32_t count = mp_decode_array(data);
 	lua_createtable(L, count, 0);
-	struct tuple_format *format =
-		box_tuple_format_default();
 	for (uint32_t j = 0; j < count; ++j) {
 		const char *begin = *data;
 		mp_next(data);
@@ -618,6 +617,15 @@ static int
 netbox_decode_select(struct lua_State *L)
 {
 	uint32_t ctypeid;
+	int top = lua_gettop(L);
+	assert(top == 1 || top == 2);
+	struct tuple_format *format;
+	if (top == 2 && lua_type(L, 2) == LUA_TCDATA) {
+		format = *(struct tuple_format **)luaL_checkcdata(L, 2,
+								  &ctypeid);
+	} else {
+		format = tuple_format_runtime;
+	}
 	const char *data = *(const char **)luaL_checkcdata(L, 1, &ctypeid);
 	assert(mp_typeof(*data) == MP_MAP);
 	uint32_t map_size = mp_decode_map(&data);
@@ -627,7 +635,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);
 	*(const char **)luaL_pushcdata(L, ctypeid) = data;
 	return 2;
 }
@@ -716,7 +724,7 @@ 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, tuple_format_runtime);
 			rows_index = i - map_size;
 			break;
 		case IPROTO_METADATA:
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index d9838f8..30cf227 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -63,12 +63,12 @@ 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, args)
+    local response, raw_end = internal.decode_select(raw_data, args.format)
     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, args)
+    local body, raw_end = internal.decode_select(raw_data, args.format)
     if body[2] then
         return nil, raw_end, box.error.MORE_THAN_ONE_TUPLE
     end
@@ -82,6 +82,12 @@ 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_call_16(raw_data)
+    return internal.decode_select(raw_data)
+end
+local function decode_select(raw_data, raw_data_end, args)
+    return internal.decode_select(raw_data, args.format)
+end
 
 local function encode_call(send_buf, id, args)
     return internal.encode_call(send_buf, id, args.func_name, args.args)
@@ -150,7 +156,7 @@ local method_encoder = {
 
 local method_decoder = {
     ping    = decode_nil,
-    call_16 = internal.decode_select,
+    call_16 = decode_call_16,
     call_17 = decode_data,
     eval    = decode_data,
     insert  = decode_tuple,
@@ -158,7 +164,7 @@ local method_decoder = {
     delete  = decode_tuple,
     update  = decode_tuple,
     upsert  = decode_nil,
-    select  = internal.decode_select,
+    select  = decode_select,
     execute = internal.decode_execute,
     get     = decode_get,
     min     = decode_get,
@@ -623,7 +629,8 @@ 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.args)
             assert(real_end == body_end, "invalid body length")
             requests[id] = nil
             request.id = nil
@@ -1267,6 +1274,7 @@ function remote_methods:_install_schema(schema_version, spaces, indices,
         s.index = {}
         s.temporary = false
         s._format = format
+        s._format_cdata = box.internal.format_new(format)
         s.connection = self
         if #space > 5 then
             local opts = space[6]
@@ -1384,13 +1392,15 @@ space_metatable = function(remote)
 
     function methods:insert(tuple, opts)
         check_space_arg(self, 'insert')
-        local args = {space_id = self.id, tuple = tuple}
+        local args = {space_id = self.id, tuple = tuple,
+                      format = self._format_cdata}
         return remote:_request('insert', opts, args)
     end
 
     function methods:replace(tuple, opts)
         check_space_arg(self, 'replace')
-        local args = {space_id = self.id, tuple = tuple}
+        local args = {space_id = self.id, tuple = tuple,
+                      format = self._format_cdata}
         return remote:_request('replace', opts, args)
     end
 
@@ -1443,7 +1453,7 @@ index_metatable = function(remote)
         local limit = tonumber(opts and opts.limit) or 0xFFFFFFFF
         local args = {space_id = self.space.id, index_id = self.id,
                       iterator = iterator, offset = offset, limit = limit,
-                      key = key}
+                      key = key, format = self.space._format_cdata}
         return (remote:_request('select', opts, args))
     end
 
@@ -1453,7 +1463,8 @@ index_metatable = function(remote)
             error("index:get() doesn't support `buffer` argument")
         end
         local 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 = self.space._format_cdata}
         return nothing_or_data(remote:_request('get', opts, args))
     end
 
@@ -1463,7 +1474,8 @@ index_metatable = function(remote)
             error("index:min() doesn't support `buffer` argument")
         end
         local 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 = self.space._format_cdata}
         return nothing_or_data(remote:_request('min', opts, args))
     end
 
@@ -1473,7 +1485,8 @@ index_metatable = function(remote)
             error("index:max() doesn't support `buffer` argument")
         end
         local 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 = self.space._format_cdata}
         return nothing_or_data(remote:_request('max', opts, args))
     end
 
@@ -1490,14 +1503,15 @@ index_metatable = function(remote)
 
     function methods:delete(key, opts)
         check_index_arg(self, 'delete')
-        local args = {space_id = self.space.id, index_id = self.id, key = key}
+        local args = {space_id = self.space.id, index_id = self.id, key = key,
+                      format = self.space._format_cdata}
         return nothing_or_data(remote:_request('delete', opts, args))
     end
 
     function methods:update(key, oplist, opts)
         check_index_arg(self, 'update')
         local args = {space_id = self.space.id, index_id = self.id, key = key,
-                      oplist = oplist}
+                      oplist = oplist, format = self.space._format_cdata}
         return nothing_or_data(remote:_request('update', opts, args))
     end
 
diff --git a/test/box/net.box.result b/test/box/net.box.result
index e87a60b..413bdec 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -3572,6 +3572,104 @@ 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', 'space')
+---
+...
+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
+...
+-- Check that formats changes after reload.
+box.space.named:format({{name = "id2"}, {name="abc2"}})
+---
+...
+s:select()[1]:tomap()
+---
+- 1: 1
+  2: 11
+  abc: 11
+  id: 1
+...
+cn:reload_schema()
+---
+...
+s:select()[1]:tomap()
+---
+- 1: 1
+  2: 11
+  id2: 1
+  abc2: 11
+...
+cn:close()
+---
+...
+box.space.named:drop()
+---
+...
+box.schema.user.revoke('guest', 'read, write, execute', 'space')
+---
+...
+--
 -- gh-3400: long-poll input discard must not touch event loop of
 -- a closed connection.
 --
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index 6e58da8..759cc4f 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -1433,6 +1433,34 @@ box.space.test3:drop()
 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})
+box.schema.user.grant('guest', 'read, write, execute', 'space')
+cn = net.connect(box.cfg.listen)
+
+s = cn.space.named
+s:get{1}.id
+s:get{1}:tomap()
+s:insert{2,3}:tomap()
+s:replace{2,14}:tomap()
+s:update(1, {{'+', 2, 10}}):tomap()
+s:select()[1]:tomap()
+s:delete({2}):tomap()
+
+-- Check that formats changes after reload.
+box.space.named:format({{name = "id2"}, {name="abc2"}})
+s:select()[1]:tomap()
+cn:reload_schema()
+s:select()[1]:tomap()
+
+cn:close()
+box.space.named:drop()
+box.schema.user.revoke('guest', 'read, write, execute', 'space')
+
+--
 -- gh-3400: long-poll input discard must not touch event loop of
 -- a closed connection.
 --

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v1 0/2] netbox: define formats for tuple from netbox
  2019-06-11  8:55 ` [PATCH v1 0/2] " Vladimir Davydov
@ 2019-06-14 12:32   ` Mergen Imeev
  0 siblings, 0 replies; 12+ messages in thread
From: Mergen Imeev @ 2019-06-14 12:32 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

Hi! Thank you for review. I'm sorry, I haven't looked at my
patch properly before sending it last time.

https://github.com/tarantool/tarantool/issues/2978
https://github.com/tarantool/tarantool/tree/imeevma/gh-2978-formats-for-tuples-from-net-box

On Tue, Jun 11, 2019 at 11:55:52AM +0300, Vladimir Davydov wrote:
> On Mon, Jun 10, 2019 at 01:02:21PM +0300, imeevma@tarantool.org wrote:
> > This patch defines field names for the tuples obtained through
> > the netbox.
> 
> Links to the branch and the ticket are missing.
> 
> > 
> > Mergen Imeev (2):
> >   netbox: store method_encoder args in request
> >   netbox: define formats for tuple from netbox
> > 
> >  src/box/lua/net_box.c     |  87 ++++++++++++++++-
> >  src/box/lua/net_box.lua   | 232 +++++++++++++++++++++++++++++++++-------------
> >  test/box/access.result    |  15 ++-
> >  test/box/access.test.lua  |   9 +-
> >  test/box/net.box.result   |  83 ++++++++++++++++-
> >  test/box/net.box.test.lua |  26 +++++-
> >  6 files changed, 369 insertions(+), 83 deletions(-)

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v1 1/2] netbox: store method_encoder args in request
  2019-06-14 11:50     ` Mergen Imeev
@ 2019-06-18  8:38       ` Vladimir Davydov
  0 siblings, 0 replies; 12+ messages in thread
From: Vladimir Davydov @ 2019-06-18  8:38 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

On Fri, Jun 14, 2019 at 02:50:23PM +0300, Mergen Imeev wrote:
> New patch:
> 
> From 8be1560eb5ed57d397c5f2da97112b448505080c Mon Sep 17 00:00:00 2001
> Date: Wed, 5 Jun 2019 13:31:07 +0300
> Subject: [PATCH] netbox: store method_encoder args in request
> 
> This patch changes the way arguments are passed to functions in
> the array method_encoder, and saves these arguments in REQUEST.
> This is necessary to establish a connection between netbox space
> objects and the tuples obtained through the netbox
> 
> Needed for #2978

Looks good to me now, thanks.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v1 2/2] netbox: define formats for tuple from netbox
  2019-06-14 12:29     ` Mergen Imeev
@ 2019-06-18  8:55       ` Vladimir Davydov
  2019-06-18  9:00       ` Vladimir Davydov
  1 sibling, 0 replies; 12+ messages in thread
From: Vladimir Davydov @ 2019-06-18  8:55 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

On Fri, Jun 14, 2019 at 03:29:21PM +0300, Mergen Imeev wrote:
> First new patch:

It looks like it's time to send v2 in a separate thread.

See a few minor comments inline.

> 
> From 63075deb8411643d4af0cf27be2c024b5a20222c Mon Sep 17 00:00:00 2001
> Date: Tue, 11 Jun 2019 15:21:39 +0300
> Subject: [PATCH] lua: internal function to parse space format
> 
> This patch defines a new function that parses space format and
> returns it to Lua as cdata. For this cdata destructor is included.
> 
> Needed for #2978
> 
> diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc
> index 8de7401..e62e2ba 100644
> --- a/src/box/lua/misc.cc
> +++ b/src/box/lua/misc.cc
> @@ -37,9 +37,13 @@
>  
>  #include "box/box.h"
>  #include "box/port.h"
> +#include "box/tuple.h"
> +#include "box/tuple_format.h"
>  #include "box/lua/tuple.h"
>  #include "mpstream.h"
>  
> +uint32_t CTID_STRUCT_TUPLE_FORMAT_PTR;
> +

Should be static.

>  /** {{{ Miscellaneous utils **/
>  
>  char *
> @@ -116,14 +120,117 @@ lbox_select(lua_State *L)
>  
>  /* }}} */
>  
> +static int
> +lbox_tuple_format_gc(struct lua_State *L)
> +{
> +	uint32_t ctypeid;
> +	struct tuple_format *format =
> +		*(struct tuple_format **)luaL_checkcdata(L, 1, &ctypeid);
> +	if (ctypeid != CTID_STRUCT_TUPLE_FORMAT_PTR) {
> +		luaL_error(L, "Invalid argument: 'struct tuple_format *' "\

Backslash (\) is not necessary.

Anyway, I think we better factor this check out into a separate
function, like lboxk_check_tuple_format, so that we could easily
reuse it in case we introduce some new functions taking a format.

> +			   "expected, got %s)",
> +			   lua_typename(L, lua_type(L, 1)));
> +	}
> +	box_tuple_format_unref(format);

Please use tuple_format_ref/tuple_format_unref.

box_* functions exist for external modules. No need to use them here.

> +	return 0;
> +}
> +
> +static int
> +lbox_push_tuple_format(struct lua_State *L, struct tuple_format *format)
> +{
> +	struct tuple_format **ptr = (struct tuple_format **)
> +		luaL_pushcdata(L, CTID_STRUCT_TUPLE_FORMAT_PTR);
> +	*ptr = format;
> +	box_tuple_format_ref(format);
> +	lua_pushcfunction(L, lbox_tuple_format_gc);
> +	luaL_setcdatagc(L, -2);
> +	return 1;
> +}
> +
> +static int
> +lbox_format_new(struct lua_State *L)

Please be consistent and use tuple_format prefix everywhere.

> +{
> +	assert(CTID_STRUCT_TUPLE_FORMAT_PTR != 0);
> +	if (lua_gettop(L) != 1 || ! lua_istable(L, 1))
> +		return luaL_error(L, "Usage: box.internal.format_new(format)");

I would rather name this function box.internal.new_tuple_format.

Also, I think we should allow calling it without any arguments, in which
case it should be equivalent to box.internal.new_tuple_format({}).

> +	uint32_t count = lua_objlen(L, 1);
> +	if (count == 0)
> +		return lbox_push_tuple_format(L, tuple_format_runtime);
> +	size_t size = count * sizeof(struct field_def);
> +	struct region *region = &fiber()->gc;
> +	size_t region_svp = region_used(region);
> +	struct field_def *fields =
> +		(struct field_def *)region_alloc(region, size);
> +	if (fields == NULL) {
> +		diag_set(OutOfMemory, size, "region_alloc", "fields");
> +		return luaT_error(L);
> +	}
> +	for (uint32_t i = 0; i < count; ++i) {
> +		size_t len;
> +
> +		fields[i] = field_def_default;
> +
> +		lua_pushinteger(L, i + 1);
> +		lua_gettable(L, 1);
> +
> +		lua_pushstring(L, "type");
> +		lua_gettable(L, -2);
> +		if (! lua_isnil(L, -1)) {
> +			const char *type_name = lua_tolstring(L, -1, &len);
> +			fields[i].type = field_type_by_name(type_name, len);
> +			if (fields[i].type == field_type_MAX) {
> +				const char *err =
> +					"field %d has unknown field type";
> +				return luaL_error(L, tt_sprintf(err, i + 1));
> +			}
> +		}
> +		lua_pop(L, 1);
> +
> +		lua_pushstring(L, "name");
> +		lua_gettable(L, -2);
> +		if (lua_isnil(L, -1)) {
> +			return luaL_error(L, tt_sprintf("field %d name is not "\

Useless backslash (\).

> +							"specified", i + 1));
> +		}
> +		const char *name = lua_tolstring(L, -1, &len);
> +		fields[i].name = (char *)region_alloc(region, len + 1);
> +		if (fields == NULL) {
> +			diag_set(OutOfMemory, size, "region_alloc",
> +				 "fields[i].name");
> +			return luaT_error(L);
> +		}
> +		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);
> +	region_truncate(region, region_svp);
> +	if (dict == NULL)
> +		return luaT_error(L);
> +	struct tuple_format *format =
> +		tuple_format_new(&tuple_format_runtime->vtab, NULL, NULL, 0,
> +				 NULL, 0, 0, dict, false, false);
> +	if (format == NULL)
> +		return luaT_error(L);
> +	return lbox_push_tuple_format(L, format);
> +}
> +
>  void
>  box_lua_misc_init(struct lua_State *L)
>  {
>  	static const struct luaL_Reg boxlib_internal[] = {
>  		{"select", lbox_select},
> +		{"format_new", lbox_format_new},
>  		{NULL, NULL}
>  	};
>  
>  	luaL_register(L, "box.internal", boxlib_internal);
>  	lua_pop(L, 1);
> +
> +	int rc = luaL_cdef(L, "struct tuple_format;");
> +	assert(rc == 0);
> +	(void) rc;
> +	CTID_STRUCT_TUPLE_FORMAT_PTR = luaL_ctypeid(L, "struct tuple_format *");
> +	assert(CTID_STRUCT_TUPLE_FORMAT_PTR != 0);
>  }
> diff --git a/test/box/misc.result b/test/box/misc.result
> index 43b5a4a..ce39bbb 100644
> --- a/test/box/misc.result
> +++ b/test/box/misc.result
> @@ -1271,3 +1271,51 @@ box.cfg{too_long_threshold = too_long_threshold}
>  s:drop()
>  ---
>  ...
> +--
> +-- gh-2978: Function to parse space format.
> +--
> +format = {}
> +---
> +...
> +format[1] = {}
> +---
> +...
> +format[1].type = 'unsigned'
> +---
> +...
> +box.internal.format_new(format)
> +---
> +- error: field 1 name is not specified
> +...
> +format[1].name = 'aaa'
> +---
> +...
> +format[2] = {}
> +---
> +...
> +format[2].name = 'aaa'
> +---
> +...
> +format[2].type = 'any'
> +---
> +...
> +box.internal.format_new(format)
> +---
> +- error: Space field 'aaa' is duplicate
> +...
> +format[2].name = 'bbb'
> +---
> +...
> +format[3] = {}
> +---
> +...
> +format[3].name = 'ccc'
> +---
> +...
> +format[3].type = 'something'
> +---
> +...
> +box.internal.format_new(format)
> +---
> +- error: field 3 has unknown field type
> +...

Please write more tests:
 - setting 'format' without 'type'
 - passing something different from a table to the function
 - passing the value returned by space:format() to this function
 - setting is_nullable in the input table

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v1 2/2] netbox: define formats for tuple from netbox
  2019-06-14 12:29     ` Mergen Imeev
  2019-06-18  8:55       ` Vladimir Davydov
@ 2019-06-18  9:00       ` Vladimir Davydov
  1 sibling, 0 replies; 12+ messages in thread
From: Vladimir Davydov @ 2019-06-18  9:00 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

On Fri, Jun 14, 2019 at 03:29:21PM +0300, Mergen Imeev wrote:
> Second new patch:
> 
> From f9e959e3f0f38e8f6b8f1294ba29c28d41f30968 Mon Sep 17 00:00:00 2001
> Date: Tue, 11 Jun 2019 16:36:39 +0300
> Subject: [PATCH] netbox: define formats for tuple from netbox
> 
> This patch creates tuple_formats for the tuples obtained through
> the netbox.
> 
> Closes #2978
> 
> @TarantoolBot document
> Title: Field names for tuples received from net.box
> 
> It is possible now to access by field name for tuples received
> from net.box. For example:
> 
> box.cfg{listen = 3302}
> box.schema.user.grant('guest','read, write, execute', 'space')
> box.schema.user.grant('guest', 'create', 'space')
> 
> box.schema.create_space("named", {format = {{name = "id"}}})
> box.space.named:create_index('id', {parts = {{1, 'unsigned'}}})
> box.space.named:insert({1})
> require('net.box').connect('localhost', 3302).space.named:get(1).id
> 
> Result:
> 
> tarantool> require('net.box').connect('localhost', 3302).space.named:get(1).id
> ---
> - 1
> ...
> 
> diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
> index 7484a86..946d397 100644
> --- a/src/box/lua/net_box.c
> +++ b/src/box/lua/net_box.c
> @@ -590,12 +590,11 @@ 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,
> +		   struct tuple_format *format)
>  {
>  	uint32_t count = mp_decode_array(data);
>  	lua_createtable(L, count, 0);
> -	struct tuple_format *format =
> -		box_tuple_format_default();
>  	for (uint32_t j = 0; j < count; ++j) {
>  		const char *begin = *data;
>  		mp_next(data);
> @@ -618,6 +617,15 @@ static int
>  netbox_decode_select(struct lua_State *L)
>  {
>  	uint32_t ctypeid;
> +	int top = lua_gettop(L);
> +	assert(top == 1 || top == 2);
> +	struct tuple_format *format;
> +	if (top == 2 && lua_type(L, 2) == LUA_TCDATA) {
> +		format = *(struct tuple_format **)luaL_checkcdata(L, 2,
> +								  &ctypeid);

I think we should use lbox_check_tuple_format helper from the previous
patch here.

Other than that, this patch looks good to me.

> +	} else {
> +		format = tuple_format_runtime;
> +	}
>  	const char *data = *(const char **)luaL_checkcdata(L, 1, &ctypeid);
>  	assert(mp_typeof(*data) == MP_MAP);
>  	uint32_t map_size = mp_decode_map(&data);

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2019-06-18  9:00 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-06-10 10:02 [PATCH v1 0/2] netbox: define formats for tuple from netbox 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
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox