Skip to content

Commit

Permalink
net.box: fix schema fetching from 1.10/2.1 servers
Browse files Browse the repository at this point in the history
After 2.2.0-390-ga7c855e5b ("net.box: fetch '_vcollation' sysview into
the module") net.box fetches _vcollation view unconditionally, while the
view was added in 2.2.0-389-g3e3ef182f and, say, tarantool-1.10 and
tarantool-2.1 do not have it. This leads to a runtime error "Space '277'
does not exist" on a newer client that connects to an older server.

Now the view is fetched conditionally depending of a version of a
server: if it is above 2.2.1, then net.box will fetch it. Note: at the
time there are no release with a number above 2.2.1.

When _vcollation view is available, a collation in an index part will be
shown by its name (with 'collation' field), otherwise it will be shown
by its ID (in 'collation_id' field). For example:

Connect to tarantool 1.10:

 | tarantool> connection = require('net.box').connect('localhost:3301')
 | ---
 | ...
 |
 | tarantool> connection.space.s.index.sk.parts
 | ---
 | - - type: string
 |     is_nullable: false
 |     collation_id: 2
 |     fieldno: 2
 | ...

Connect to tarantool 2.2.1 (when it will be released):

 | tarantool> connection = require('net.box').connect('localhost:3301')
 | ---
 | ...
 |
 | tarantool> connection.space.s.index.sk.parts
 | ---
 | - - type: string
 |     is_nullable: false
 |     collation: unicode_ci
 |     fieldno: 2
 | ...

Fixes #4307.
  • Loading branch information
Totktonada committed Jul 30, 2019
1 parent fc355a2 commit fff809e
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 19 deletions.
37 changes: 30 additions & 7 deletions src/box/lua/net_box.lua
Expand Up @@ -84,6 +84,14 @@ local function decode_push(raw_data)
return response[IPROTO_DATA_KEY][1], raw_end
end

local function version_id(major, minor, patch)
return bit.bor(bit.lshift(major, 16), bit.lshift(minor, 8), patch)
end

local function version_at_least(peer_version_id, major, minor, patch)
return peer_version_id >= version_id(major, minor, patch)
end

local method_encoder = {
ping = internal.encode_ping,
call_16 = internal.encode_call_16,
Expand Down Expand Up @@ -735,17 +743,23 @@ local function create_transport(host, port, user, password, callback,
set_state('active')
return iproto_sm(schema_version)
end
-- _vcollation view was added in 2.2.0-389-g3e3ef182f
local peer_has_vcollation = version_at_least(greeting.version_id,
2, 2, 1)
local select1_id = new_request_id()
local select2_id = new_request_id()
local select3_id = new_request_id()
local select3_id
local response = {}
-- fetch everything from space _vspace, 2 = ITER_ALL
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)
-- fetch everything from space _vcollation, 2 = ITER_ALL
encode_select(send_buf, select3_id, VCOLLATION_ID, 0, 2, 0, 0xFFFFFFFF,
nil)
if peer_has_vcollation then
select3_id = new_request_id()
encode_select(send_buf, select3_id, VCOLLATION_ID, 0, 2, 0,
0xFFFFFFFF, nil)
end

schema_version = nil -- any schema_version will do provided that
-- it is consistent across responses
Expand All @@ -754,6 +768,8 @@ local function create_transport(host, port, user, password, callback,
if err then return error_sm(err, hdr) end
dispatch_response_iproto(hdr, body_rpos, body_end)
local id = hdr[IPROTO_SYNC_KEY]
-- trick: omit check for peer_has_vcollation: id is
-- not nil
if id == select1_id or id == select2_id or id == select3_id then
-- response to a schema query we've submitted
local status = hdr[IPROTO_STATUS_KEY]
Expand All @@ -774,9 +790,10 @@ local function create_transport(host, port, user, password, callback,
response[id] = body[IPROTO_DATA_KEY]
end
until response[select1_id] and response[select2_id] and
response[select3_id]
(not peer_has_vcollation or response[select3_id])
-- trick: response[select3_id] is nil when the key is nil
callback('did_fetch_schema', schema_version, response[select1_id],
response[select2_id],response[select3_id])
response[select2_id], response[select3_id])
set_state('active')
return iproto_sm(schema_version)
end
Expand Down Expand Up @@ -1269,17 +1286,23 @@ function remote_methods:_install_schema(schema_version, spaces, indices,
local pkcollationid = index[PARTS][k].collation
local pktype = index[PARTS][k][2] or index[PARTS][k].type
local pkfield = index[PARTS][k][1] or index[PARTS][k].field
-- resolve a collation name if a peer has
-- _vcollation view
local pkcollation = nil
if pkcollationid ~= nil then
if pkcollationid ~= nil and collations ~= nil then
pkcollation = collations[pkcollationid + 1][2]
end

local pk = {
type = pktype,
fieldno = pkfield + 1,
collation = pkcollation,
is_nullable = pknullable
}
if collations == nil then
pk.collation_id = pkcollationid
else
pk.collation = pkcollation
end
idx.parts[k] = pk
end
idx.unique = not not index[OPTS].unique
Expand Down
33 changes: 28 additions & 5 deletions test/box/net.box.result
Expand Up @@ -2905,18 +2905,41 @@ c = net:connect(box.cfg.listen)
box.internal.collation.create('test', 'ICU', 'ru-RU')
---
...
collation_id = box.internal.collation.id_by_name('test')
---
...
_ = space:create_index('sk', { type = 'tree', parts = {{1, 'str', collation = 'test'}}, unique = true })
---
...
c:reload_schema()
---
...
c.space.test.index.sk.parts
parts = c.space.test.index.sk.parts
---
- - type: string
is_nullable: false
collation: test
fieldno: 1
...
#parts == 1
---
- true
...
parts[1].fieldno == 1
---
- true
...
parts[1].type == 'string'
---
- true
...
parts[1].is_nullable == false
---
- true
...
if _TARANTOOL >= '2.2.1' then \
return parts[1].collation == 'test' \
else \
return parts[1].collation_id == collation_id \
end
---
- true
...
c:close()
---
Expand Down
12 changes: 11 additions & 1 deletion test/box/net.box.test.lua
Expand Up @@ -1195,9 +1195,19 @@ c:close()
box.schema.user.grant('guest', 'read', 'space', 'test')
c = net:connect(box.cfg.listen)
box.internal.collation.create('test', 'ICU', 'ru-RU')
collation_id = box.internal.collation.id_by_name('test')
_ = space:create_index('sk', { type = 'tree', parts = {{1, 'str', collation = 'test'}}, unique = true })
c:reload_schema()
c.space.test.index.sk.parts
parts = c.space.test.index.sk.parts
#parts == 1
parts[1].fieldno == 1
parts[1].type == 'string'
parts[1].is_nullable == false
if _TARANTOOL >= '2.2.1' then \
return parts[1].collation == 'test' \
else \
return parts[1].collation_id == collation_id \
end
c:close()
box.internal.collation.drop('test')
space:drop()
Expand Down
11 changes: 7 additions & 4 deletions test/box/stat_net.result
Expand Up @@ -83,9 +83,9 @@ box.stat.net.CONNECTIONS.total
---
- 4
...
box.stat.net.REQUESTS.total
box.stat.net.REQUESTS.total > 0
---
- 17
- true
...
box.stat.net.CONNECTIONS.current
---
Expand Down Expand Up @@ -115,6 +115,9 @@ test_run:wait_cond(function() return box.stat.net.CONNECTIONS.current == 1 end,
---
- true
...
requests_total_saved = box.stat.net.REQUESTS.total
---
...
future1 = cn:call('tweedledee', {}, {is_async = true})
---
...
Expand Down Expand Up @@ -149,9 +152,9 @@ test_run:wait_cond(function() return box.stat.net.REQUESTS.current == 0 end, WAI
---
- true
...
box.stat.net.REQUESTS.total
box.stat.net.REQUESTS.total - requests_total_saved == 2
---
- 19
- true
...
-- reset
box.stat.reset()
Expand Down
5 changes: 3 additions & 2 deletions test/box/stat_net.test.lua
Expand Up @@ -29,7 +29,7 @@ cn.space.tweedledum:select() --small request
box.stat.net.SENT.total > 0
box.stat.net.RECEIVED.total > 0
box.stat.net.CONNECTIONS.total
box.stat.net.REQUESTS.total
box.stat.net.REQUESTS.total > 0
box.stat.net.CONNECTIONS.current
box.stat.net.REQUESTS.current

Expand All @@ -41,6 +41,7 @@ test_run:wait_cond(function() return box.stat.net.CONNECTIONS.current == 2 end,
cn3:close()
test_run:wait_cond(function() return box.stat.net.CONNECTIONS.current == 1 end, WAIT_COND_TIMEOUT)

requests_total_saved = box.stat.net.REQUESTS.total
future1 = cn:call('tweedledee', {}, {is_async = true})
test_run:wait_cond(function() return box.stat.net.REQUESTS.current == 1 end, WAIT_COND_TIMEOUT)
future2 = cn:call('tweedledee', {}, {is_async = true})
Expand All @@ -50,7 +51,7 @@ ch:put(true)
future1:wait_result()
future2:wait_result()
test_run:wait_cond(function() return box.stat.net.REQUESTS.current == 0 end, WAIT_COND_TIMEOUT)
box.stat.net.REQUESTS.total
box.stat.net.REQUESTS.total - requests_total_saved == 2

-- reset
box.stat.reset()
Expand Down

0 comments on commit fff809e

Please sign in to comment.