Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit 050ee8e

Browse files
committed
Support of nesting a record into a record
* Related to #46. * Fixes #49.
1 parent a4be6ee commit 050ee8e

7 files changed

+356
-39
lines changed

.luacheckrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ std = {
33
'tonumber', 'type', 'assert', 'ipairs', 'math', 'error', 'string',
44
'table', 'pairs', 'os', 'select', 'unpack', 'dofile', 'next',
55
'getmetatable', 'setmetatable', 'rawget', 'print', 'shard_status',
6-
'loadstring'
6+
'loadstring',
77
},
88
globals = {'package'}
99
}

graphql/tarantool_graphql.lua

+38-15
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,7 @@ end
105105

106106
--- Non-recursive version of the @{gql_type} function that returns
107107
--- InputObject instead of Object.
108-
local function gql_argument_type(state, avro_schema)
109-
assert(type(state) == 'table',
110-
'state must be a table, got ' .. type(state))
108+
local function gql_argument_type(avro_schema)
111109
assert(avro_schema ~= nil,
112110
'avro_schema must not be nil')
113111

@@ -149,27 +147,53 @@ local function gql_argument_type(state, avro_schema)
149147
end
150148
end
151149

152-
local function convert_record_fields_to_args(state, fields)
150+
--- Convert each field of an avro-schema to a scalar graphql type or an input
151+
--- object.
152+
---
153+
--- @tparam table fields list of fields the avro-schema record fields format
154+
---
155+
--- @tparam[opt] table opts optional options:
156+
---
157+
--- * skip_compound -- do not add fields of record type to the arguments;
158+
--- default: false.
159+
---
160+
--- @treturn table `args` -- map with type names as keys and graphql types as
161+
--- values
162+
local function convert_record_fields_to_args(fields, opts)
163+
assert(type(fields) == 'table',
164+
'fields must be a table, got ' .. type(fields))
165+
166+
local opts = opts or {}
167+
assert(type(opts) == 'table',
168+
'opts must be a table, got ' .. type(opts))
169+
170+
local skip_compound = opts.skip_compound or false
171+
assert(type(skip_compound) == 'boolean',
172+
'skip_compound must be a boolean, got ' .. type(skip_compound))
173+
153174
local args = {}
154175
for _, field in ipairs(fields) do
155176
assert(type(field.name) == 'string',
156177
('field.name must be a string, got %s (schema %s)')
157178
:format(type(field.name), json.encode(field)))
158-
local gql_class = gql_argument_type(state, field.type)
159-
args[field.name] = nullable(gql_class)
179+
if not skip_compound or avro_type(field.type) ~= 'record' then
180+
local gql_class = gql_argument_type(field.type)
181+
args[field.name] = nullable(gql_class)
182+
end
160183
end
161184
return args
162185
end
163186

164-
--- Convert each field of an avro-schema to a graphql type and corresponding
165-
--- argument for an upper graphql type.
187+
--- Convert each field of an avro-schema to a graphql type
166188
---
167189
--- @tparam table state for read state.accessor and previously filled
168190
--- state.types
169191
--- @tparam table fields fields part from an avro-schema
192+
---
193+
--- @treturn table `res` -- map with type names as keys and graphql types as
194+
--- values
170195
local function convert_record_fields(state, fields)
171196
local res = {}
172-
local object_args = {}
173197
for _, field in ipairs(fields) do
174198
assert(type(field.name) == 'string',
175199
('field.name must be a string, got %s (schema %s)')
@@ -178,9 +202,8 @@ local function convert_record_fields(state, fields)
178202
name = field.name,
179203
kind = gql_type(state, field.type),
180204
}
181-
object_args[field.name] = nullable(res[field.name].kind)
182205
end
183-
return res, object_args
206+
return res
184207
end
185208

186209
--- The function recursively converts passed avro-schema to a graphql type.
@@ -228,7 +251,7 @@ gql_type = function(state, avro_schema, collection, collection_name)
228251
('avro_schema.fields must be a table, got %s (avro_schema %s)')
229252
:format(type(avro_schema.fields), json.encode(avro_schema)))
230253

231-
local fields, _ = convert_record_fields(state, avro_schema.fields)
254+
local fields = convert_record_fields(state, avro_schema.fields)
232255

233256
for _, c in ipairs((collection or {}).connections or {}) do
234257
assert(type(c.type) == 'string',
@@ -371,10 +394,10 @@ local function parse_cfg(cfg)
371394
schema.name))
372395
state.types[name] = gql_type(state, schema, collection, name)
373396

374-
local _, object_args = convert_record_fields(state,
375-
schema.fields)
397+
local object_args = convert_record_fields_to_args(schema.fields,
398+
{skip_compound = true})
376399
local list_args = convert_record_fields_to_args(
377-
state, accessor:list_args(name))
400+
accessor:list_args(name))
378401
local args = utils.merge_tables(object_args, list_args)
379402
state.object_arguments[name] = object_args
380403
state.list_arguments[name] = list_args

test/common/lua/multirunner.lua

+40-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env tarantool
2+
23
local net_box = require('net.box')
3-
local shard = require('shard')
4+
5+
local initialized = false
46

57
local function instance_uri(instance_id)
68
local socket_dir = require('fio').cwd()
@@ -9,13 +11,21 @@ local function instance_uri(instance_id)
911
end
1012

1113
local function init_shard(test_run, servers, config)
12-
local shard = require('shard')
13-
local suite = "common"
14+
local suite = 'common'
1415
test_run:create_cluster(servers, suite)
15-
box.once('init_shard_module', function()
16+
17+
-- XXX: for now we always use one shard configuration (a first one),
18+
-- because it is unclear how to reload shard module with an another
19+
-- configuration; the another way is use several test configurations, but
20+
-- it seems to be non-working in 'core = app' tests with current test-run.
21+
-- Ways to better handle this is subject to future digging.
22+
local shard = require('shard')
23+
if not initialized then
1624
shard.init(config)
17-
end)
25+
initialized = true
26+
end
1827
shard.wait_connection()
28+
return shard
1929
end
2030

2131
local function shard_cleanup(test_run, servers)
@@ -29,6 +39,14 @@ local function shard_cleanup(test_run, servers)
2939
end
3040
end
3141

42+
local function for_each_server(shard, func)
43+
for _, zone in ipairs(shard.shards) do
44+
for _, node in ipairs(zone) do
45+
func(node.uri)
46+
end
47+
end
48+
end
49+
3250
-- Run tests on multiple accessors and configurations.
3351
-- Feel free to add more configurations.
3452
local function run(test_run, init_function, cleanup_function, callback)
@@ -38,8 +56,8 @@ local function run(test_run, init_function, cleanup_function, callback)
3856

3957
local servers = {'shard1', 'shard2', 'shard3', 'shard4'};
4058

41-
-- Test sharding without redundancy = 2.
42-
init_shard(test_run, servers, {
59+
-- Test sharding with redundancy = 2.
60+
local shard = init_shard(test_run, servers, {
4361
servers = {
4462
{ uri = instance_uri('1'), zone = '0' },
4563
{ uri = instance_uri('2'), zone = '1' },
@@ -50,23 +68,23 @@ local function run(test_run, init_function, cleanup_function, callback)
5068
password = '',
5169
redundancy = 2,
5270
monitor = false
53-
});
71+
})
5472

55-
for _, shard_server in ipairs(shard_status().online) do
56-
local c = net_box.connect(shard_server.uri)
73+
for_each_server(shard, function(uri)
74+
local c = net_box.connect(uri)
5775
c:eval(init_script)
58-
end
76+
end)
5977

6078
callback("Shard 2x2", shard)
6179

62-
for _, shard_server in ipairs(shard_status().online) do
63-
local c = net_box.connect(shard_server.uri)
80+
for_each_server(shard, function(uri)
81+
local c = net_box.connect(uri)
6482
c:eval(cleanup_script)
65-
end
83+
end)
6684
shard_cleanup(test_run, servers)
6785

6886
-- Test sharding without redundancy.
69-
init_shard(test_run, servers, {
87+
local shard = init_shard(test_run, servers, {
7088
servers = {
7189
{ uri = instance_uri('1'), zone = '0' },
7290
{ uri = instance_uri('2'), zone = '1' },
@@ -77,19 +95,19 @@ local function run(test_run, init_function, cleanup_function, callback)
7795
password = '',
7896
redundancy = 1,
7997
monitor = false
80-
});
98+
})
8199

82-
for _, shard_server in ipairs(shard_status().online) do
83-
local c = net_box.connect(shard_server.uri)
100+
for_each_server(shard, function(uri)
101+
local c = net_box.connect(uri)
84102
c:eval(init_script)
85-
end
103+
end)
86104

87105
callback("Shard 4x1", shard)
88106

89-
for _, shard_server in ipairs(shard_status().online) do
90-
local c = net_box.connect(shard_server.uri)
107+
for_each_server(shard, function(uri)
108+
local c = net_box.connect(uri)
91109
c:eval(cleanup_script)
92-
end
110+
end)
93111
shard_cleanup(test_run, servers)
94112

95113
-- Test local setup (box).
+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
-- Nested record inside a record
2+
-- https://github.com/tarantool/graphql/issues/46
3+
-- https://github.com/tarantool/graphql/issues/49
4+
5+
local json = require('json')
6+
local yaml = require('yaml')
7+
local utils = require('graphql.utils')
8+
9+
local testdata = {}
10+
11+
testdata.meta = {
12+
schemas = json.decode([[{
13+
"user": {
14+
"type": "record",
15+
"name": "user",
16+
"fields": [
17+
{"name": "uid", "type": "long"},
18+
{"name": "p1", "type": "string"},
19+
{"name": "p2", "type": "string"},
20+
{
21+
"name": "nested",
22+
"type": {
23+
"type": "record",
24+
"name": "nested",
25+
"fields": [
26+
{"name": "x", "type": "long"},
27+
{"name": "y", "type": "long"}
28+
]
29+
}
30+
}
31+
]
32+
}
33+
}]]),
34+
collections = json.decode([[{
35+
"user": {
36+
"schema_name": "user",
37+
"connections": []
38+
}
39+
}]]),
40+
service_fields = {
41+
user = {},
42+
},
43+
indexes = {
44+
user = {
45+
uid = {
46+
service_fields = {},
47+
fields = {'uid'},
48+
index_type = 'tree',
49+
unique = true,
50+
primary = true,
51+
},
52+
},
53+
}
54+
}
55+
56+
57+
function testdata.init_spaces()
58+
-- user fields
59+
local UID_FN = 1
60+
61+
box.schema.create_space('user')
62+
box.space.user:create_index('uid', {
63+
type = 'tree', unique = true, parts = {UID_FN, 'unsigned'}})
64+
end
65+
66+
function testdata.drop_spaces()
67+
box.space.user:drop()
68+
end
69+
70+
function testdata.fill_test_data(virtbox)
71+
for i = 1, 15 do
72+
local uid = i
73+
local p1 = 'p1 ' .. tostring(i)
74+
local p2 = 'p2 ' .. tostring(i)
75+
local x = 1000 + i
76+
local y = 2000 + i
77+
virtbox.user:replace({uid, p1, p2, x, y})
78+
end
79+
end
80+
81+
function testdata.run_queries(gql_wrapper)
82+
local output = ''
83+
84+
local query_1 = [[
85+
query getUserByUid($uid: Long) {
86+
user(uid: $uid) {
87+
uid
88+
p1
89+
p2
90+
nested {
91+
x
92+
y
93+
}
94+
}
95+
}
96+
]]
97+
98+
local variables_1 = {uid = 5}
99+
local result_1 = utils.show_trace(function()
100+
local gql_query_1 = gql_wrapper:compile(query_1)
101+
return gql_query_1:execute(variables_1)
102+
end)
103+
104+
output = output .. 'RUN 1 {{{\n' ..
105+
(('QUERY\n%s'):format(query_1:rstrip())) .. '\n' ..
106+
(('VARIABLES\n%s'):format(yaml.encode(variables_1))) .. '\n' ..
107+
(('RESULT\n%s'):format(yaml.encode(result_1))) .. '\n' ..
108+
'}}}\n'
109+
110+
--[=[
111+
local query_2 = [[
112+
query getUserByX($x: Long) {
113+
user(nested: {x: $x}) {
114+
uid
115+
p1
116+
p2
117+
nested {
118+
x
119+
y
120+
}
121+
}
122+
}
123+
]]
124+
125+
local variables_2 = {x = 1005}
126+
local result_2 = utils.show_trace(function()
127+
local gql_query_2 = gql_wrapper:compile(query_2)
128+
return gql_query_2:execute(variables_2)
129+
end)
130+
131+
output = output .. 'RUN 2 {{{\n' ..
132+
(('QUERY\n%s'):format(query_2:rstrip())) .. '\n' ..
133+
(('VARIABLES\n%s'):format(yaml.encode(variables_2))) .. '\n' ..
134+
(('RESULT\n%s'):format(yaml.encode(result_2))) .. '\n' ..
135+
'}}}\n'
136+
]=]--
137+
138+
return output:rstrip()
139+
end
140+
141+
return testdata

0 commit comments

Comments
 (0)