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

Support of nesting a record into a record #56

Merged
merged 1 commit into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ std = {
'tonumber', 'type', 'assert', 'ipairs', 'math', 'error', 'string',
'table', 'pairs', 'os', 'select', 'unpack', 'dofile', 'next',
'getmetatable', 'setmetatable', 'rawget', 'print', 'shard_status',
'loadstring'
'loadstring',
},
globals = {'package'}
}
Expand Down
53 changes: 38 additions & 15 deletions graphql/tarantool_graphql.lua
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,7 @@ end

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

Expand Down Expand Up @@ -149,27 +147,53 @@ local function gql_argument_type(state, avro_schema)
end
end

local function convert_record_fields_to_args(state, fields)
--- Convert each field of an avro-schema to a scalar graphql type or an input
--- object.
---
--- @tparam table fields list of fields of the avro-schema record fields format
---
--- @tparam[opt] table opts optional options:
---
--- * `skip_compound` -- do not add fields of record type to the arguments;
--- default: false.
---
--- @treturn table `args` -- map with type names as keys and graphql types as
--- values
local function convert_record_fields_to_args(fields, opts)
assert(type(fields) == 'table',
'fields must be a table, got ' .. type(fields))

local opts = opts or {}
assert(type(opts) == 'table',
'opts must be a table, got ' .. type(opts))

local skip_compound = opts.skip_compound or false
assert(type(skip_compound) == 'boolean',
'skip_compound must be a boolean, got ' .. type(skip_compound))

local args = {}
for _, field in ipairs(fields) do
assert(type(field.name) == 'string',
('field.name must be a string, got %s (schema %s)')
:format(type(field.name), json.encode(field)))
local gql_class = gql_argument_type(state, field.type)
args[field.name] = nullable(gql_class)
if not skip_compound or avro_type(field.type) ~= 'record' then
local gql_class = gql_argument_type(field.type)
args[field.name] = nullable(gql_class)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you call nullable without any conditions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was that you can omit any argument. But AFAIR it is so even if you don’t use 'nullable'.

end
end
return args
end

--- Convert each field of an avro-schema to a graphql type and corresponding
--- argument for an upper graphql type.
--- Convert each field of an avro-schema to a graphql type.
---
--- @tparam table state for read state.accessor and previously filled
--- state.types
--- @tparam table fields fields part from an avro-schema
---
--- @treturn table `res` -- map with type names as keys and graphql types as
--- values
local function convert_record_fields(state, fields)
local res = {}
local object_args = {}
for _, field in ipairs(fields) do
assert(type(field.name) == 'string',
('field.name must be a string, got %s (schema %s)')
Expand All @@ -178,9 +202,8 @@ local function convert_record_fields(state, fields)
name = field.name,
kind = gql_type(state, field.type),
}
object_args[field.name] = nullable(res[field.name].kind)
end
return res, object_args
return res
end

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

local fields, _ = convert_record_fields(state, avro_schema.fields)
local fields = convert_record_fields(state, avro_schema.fields)

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

local _, object_args = convert_record_fields(state,
schema.fields)
local object_args = convert_record_fields_to_args(schema.fields,
{skip_compound = true})
local list_args = convert_record_fields_to_args(
state, accessor:list_args(name))
accessor:list_args(name))
local args = utils.merge_tables(object_args, list_args)
state.object_arguments[name] = object_args
state.list_arguments[name] = list_args
Expand Down
62 changes: 40 additions & 22 deletions test/common/lua/multirunner.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env tarantool

local net_box = require('net.box')
local shard = require('shard')

local initialized = false

local function instance_uri(instance_id)
local socket_dir = require('fio').cwd()
Expand All @@ -9,13 +11,21 @@ local function instance_uri(instance_id)
end

local function init_shard(test_run, servers, config)
local shard = require('shard')
local suite = "common"
local suite = 'common'
test_run:create_cluster(servers, suite)
box.once('init_shard_module', function()

-- XXX: for now we always use one shard configuration (a first one),
-- because it is unclear how to reload shard module with an another
-- configuration; the another way is use several test configurations, but
-- it seems to be non-working in 'core = app' tests with current test-run.
-- Ways to better handle this is subject to future digging.
local shard = require('shard')
if not initialized then
shard.init(config)
end)
initialized = true
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain, please?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I added the 2nd test. When the default (master) instance stops (the suite has core = app) and starts from a snap/xlog from the previous test, the box.once will not invoke their function, so shard become uninitialized.
  2. Not it works, but is not correct. We avoid the another problem: the shard module cannot be initialized twice, so we omit the 2nd initialization and proceed twice with the same (the first one) configuration.

Copy link
Member Author

@Totktonada Totktonada Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the code with the comment.

Copy link
Member Author

@Totktonada Totktonada Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed the following issue for this problem: tarantool/graphql#57

shard.wait_connection()
return shard
end

local function shard_cleanup(test_run, servers)
Expand All @@ -29,6 +39,14 @@ local function shard_cleanup(test_run, servers)
end
end

local function for_each_server(shard, func)
for _, zone in ipairs(shard.shards) do
for _, node in ipairs(zone) do
func(node.uri)
end
end
end

-- Run tests on multiple accessors and configurations.
-- Feel free to add more configurations.
local function run(test_run, init_function, cleanup_function, callback)
Expand All @@ -38,8 +56,8 @@ local function run(test_run, init_function, cleanup_function, callback)

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

-- Test sharding without redundancy = 2.
init_shard(test_run, servers, {
-- Test sharding with redundancy = 2.
local shard = init_shard(test_run, servers, {
servers = {
{ uri = instance_uri('1'), zone = '0' },
{ uri = instance_uri('2'), zone = '1' },
Expand All @@ -50,23 +68,23 @@ local function run(test_run, init_function, cleanup_function, callback)
password = '',
redundancy = 2,
monitor = false
});
})

for _, shard_server in ipairs(shard_status().online) do
local c = net_box.connect(shard_server.uri)
for_each_server(shard, function(uri)
local c = net_box.connect(uri)
c:eval(init_script)
end
end)

callback("Shard 2x2", shard)

for _, shard_server in ipairs(shard_status().online) do
local c = net_box.connect(shard_server.uri)
for_each_server(shard, function(uri)
local c = net_box.connect(uri)
c:eval(cleanup_script)
end
end)
shard_cleanup(test_run, servers)

-- Test sharding without redundancy.
init_shard(test_run, servers, {
local shard = init_shard(test_run, servers, {
servers = {
{ uri = instance_uri('1'), zone = '0' },
{ uri = instance_uri('2'), zone = '1' },
Expand All @@ -77,19 +95,19 @@ local function run(test_run, init_function, cleanup_function, callback)
password = '',
redundancy = 1,
monitor = false
});
})

for _, shard_server in ipairs(shard_status().online) do
local c = net_box.connect(shard_server.uri)
for_each_server(shard, function(uri)
local c = net_box.connect(uri)
c:eval(init_script)
end
end)

callback("Shard 4x1", shard)

for _, shard_server in ipairs(shard_status().online) do
local c = net_box.connect(shard_server.uri)
for_each_server(shard, function(uri)
local c = net_box.connect(uri)
c:eval(cleanup_script)
end
end)
shard_cleanup(test_run, servers)

-- Test local setup (box).
Expand Down
141 changes: 141 additions & 0 deletions test/common/lua/test_data_nested_record.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
-- Nested record inside a record
-- https://github.com/tarantool/graphql/issues/46
-- https://github.com/tarantool/graphql/issues/49

local json = require('json')
local yaml = require('yaml')
local utils = require('graphql.utils')

local testdata = {}

testdata.meta = {
schemas = json.decode([[{
"user": {
"type": "record",
"name": "user",
"fields": [
{"name": "uid", "type": "long"},
{"name": "p1", "type": "string"},
{"name": "p2", "type": "string"},
{
"name": "nested",
"type": {
"type": "record",
"name": "nested",
"fields": [
{"name": "x", "type": "long"},
{"name": "y", "type": "long"}
]
}
}
]
}
}]]),
collections = json.decode([[{
"user": {
"schema_name": "user",
"connections": []
}
}]]),
service_fields = {
user = {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does user = {}, mean? Is it neccessary?

Copy link
Member Author

@Totktonada Totktonada Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty list of service fields. Don’t sure we allow to skip it, but that has sense, yep.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped for now.

},
indexes = {
user = {
uid = {
service_fields = {},
fields = {'uid'},
index_type = 'tree',
unique = true,
primary = true,
},
},
}
}


function testdata.init_spaces()
-- user fields
local UID_FN = 1

box.schema.create_space('user')
box.space.user:create_index('uid', {
type = 'tree', unique = true, parts = {UID_FN, 'unsigned'}})
end

function testdata.drop_spaces()
box.space.user:drop()
end

function testdata.fill_test_data(virtbox)
for i = 1, 15 do
local uid = i
local p1 = 'p1 ' .. tostring(i)
local p2 = 'p2 ' .. tostring(i)
local x = 1000 + i
local y = 2000 + i
virtbox.user:replace({uid, p1, p2, x, y})
end
end

function testdata.run_queries(gql_wrapper)
local output = ''

local query_1 = [[
query getUserByUid($uid: Long) {
user(uid: $uid) {
uid
p1
p2
nested {
x
y
}
}
}
]]

local variables_1 = {uid = 5}
local result_1 = utils.show_trace(function()
local gql_query_1 = gql_wrapper:compile(query_1)
return gql_query_1:execute(variables_1)
end)

output = output .. 'RUN 1 {{{\n' ..
(('QUERY\n%s'):format(query_1:rstrip())) .. '\n' ..
(('VARIABLES\n%s'):format(yaml.encode(variables_1))) .. '\n' ..
(('RESULT\n%s'):format(yaml.encode(result_1))) .. '\n' ..
'}}}\n'

--[=[
local query_2 = [[
query getUserByX($x: Long) {
user(nested: {x: $x}) {
uid
p1
p2
nested {
x
y
}
}
}
]]

local variables_2 = {x = 1005}
local result_2 = utils.show_trace(function()
local gql_query_2 = gql_wrapper:compile(query_2)
return gql_query_2:execute(variables_2)
end)

output = output .. 'RUN 2 {{{\n' ..
(('QUERY\n%s'):format(query_2:rstrip())) .. '\n' ..
(('VARIABLES\n%s'):format(yaml.encode(variables_2))) .. '\n' ..
(('RESULT\n%s'):format(yaml.encode(result_2))) .. '\n' ..
'}}}\n'
]=]--

return output:rstrip()
end

return testdata
Loading