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

Commit fcc7007

Browse files
committed
Refactor query_to_avro a bit
The changes are debatable, but I'm trying to improve code readability as stated in the comments to [1]. [1]: tarantool/graphql#58
1 parent 0aaf2b1 commit fcc7007

File tree

4 files changed

+101
-82
lines changed

4 files changed

+101
-82
lines changed

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,18 @@ make test
9595

9696
## Requirements
9797

98-
* For use: tarantool, lulpeg, >=tarantool/shard-1.1-91-gfa88bf8 (optional),
99-
tarantool/avro-schema.
100-
* For test (additionally to 'for use'): python 2.7, virtualenv, luacheck,
101-
>=tarantool/shard-1.1-92-gec1a27e.
102-
* For building apidoc (additionally to 'for use'): ldoc.
98+
* For use:
99+
* tarantool,
100+
* lulpeg,
101+
* >=tarantool/avro-schema-2.0-71-gfea0ead,
102+
* >=tarantool/shard-1.1-91-gfa88bf8 (optional).
103+
* For test (additionally to 'for use'):
104+
* python 2.7,
105+
* virtualenv,
106+
* luacheck,
107+
* >=tarantool/shard-1.1-92-gec1a27e.
108+
* For building apidoc (additionally to 'for use'):
109+
* ldoc.
103110

104111
## License
105112

graphql/query_to_avro.lua

Lines changed: 74 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
---
33
--- Random notes:
44
---
5-
--- * The best way to use this module is to just call `avro_schema` methon on
5+
--- * The best way to use this module is to just call `avro_schema` method on
66
--- compiled query object.
77
local path = "graphql.core"
88
local introspection = require(path .. '.introspection')
@@ -11,6 +11,9 @@ local query_util = require(path .. '.query_util')
1111
-- module functions
1212
local query_to_avro = {}
1313

14+
-- forward declaration
15+
local object_to_avro
16+
1417
local gql_scalar_to_avro_index = {
1518
String = "string",
1619
Int = "int",
@@ -28,79 +31,84 @@ local function gql_scalar_to_avro(fieldType)
2831
return result
2932
end
3033

31-
-- The function converts avro type to nullable.
32-
-- In current tarantool/avro-schema implementation we simply add '*'
33-
-- to the end of type name.
34-
-- The function do not copy the resulting type but changes it in place.
35-
--
36-
-- @tparam table avro schema node to be converted to nullable
37-
--
38-
-- @tresult table schema node; basically it is the passed schema node,
39-
-- however in nullable type implementation through unions it can be different
40-
-- node
34+
--- The function converts avro type to the corresponding nullable type in
35+
--- place and returns the result.
36+
---
37+
--- We make changes in place in case of table input (`avro`) because of
38+
--- performance reasons, but we returns the result because an input (`avro`)
39+
--- can be a string. Strings in Lua are immutable.
40+
---
41+
--- In the current tarantool/avro-schema implementation we simply add '*' to
42+
--- the end of a type name.
43+
---
44+
--- If the type is already nullable the function leaves it as is.
45+
---
46+
--- @tparam table avro avro schema node to be converted to nullable one
47+
---
48+
--- @result `result` (string or table) nullable avro type
4149
local function make_avro_type_nullable(avro)
42-
assert(avro.type ~= nil, "Avro `type` field is necessary")
43-
local type_type = type(avro.type)
44-
if type_type == "string" then
45-
assert(avro.type:endswith("*") == false,
46-
"Avro type should not be nullable already")
47-
avro.type = avro.type .. '*'
48-
return avro
49-
end
50-
if type_type == "table" then
51-
avro.type = make_avro_type_nullable(avro.type)
52-
return avro
50+
assert(avro ~= nil, "avro must not be nil")
51+
52+
local value_type = type(avro)
53+
54+
if value_type == "string" then
55+
return avro:endswith("*") and avro or (avro .. '*')
56+
elseif value_type == "table" then
57+
return make_avro_type_nullable(avro.type)
5358
end
54-
error("Avro type should be a string or table, got :" .. type_type)
55-
end
5659

57-
local object_to_avro
60+
error("avro should be a string or a table, got " .. value_type)
61+
end
5862

59-
local function complete_field_to_avro(fieldType, result, subSelections, context,
60-
NonNull)
63+
--- Convert GraphQL type to avro-schema with selecting fields.
64+
---
65+
--- @tparam table fieldType GraphQL type
66+
---
67+
--- @tparam table subSelections fields to select from resulting avro-schema
68+
--- (internal graphql-lua format)
69+
---
70+
--- @tparam table context current traversal context, here it just falls to the
71+
--- called functions (internal graphql-lua format)
72+
---
73+
--- @tresult table `result` is the resulting avro-schema
74+
local function gql_type_to_avro(fieldType, subSelections, context)
6175
local fieldTypeName = fieldType.__type
62-
if fieldTypeName == 'NonNull' then
63-
-- In case the field is NonNull, the real type is in ofType attribute.
76+
local isNonNull = false
77+
78+
-- In case the field is NonNull, the real type is in ofType attribute.
79+
while fieldTypeName == 'NonNull' do
6480
fieldType = fieldType.ofType
6581
fieldTypeName = fieldType.__type
66-
elseif NonNull ~= true then
67-
-- Call complete_field second time and make result nullable.
68-
result = complete_field_to_avro(fieldType, result, subSelections,
69-
context, true)
70-
result = make_avro_type_nullable(result)
71-
return result
82+
isNonNull = true
7283
end
7384

85+
local result
86+
7487
if fieldTypeName == 'List' then
7588
local innerType = fieldType.ofType
76-
-- Steal type from virtual object.
77-
-- This is necessary because in case of arrays type should be
78-
-- "completed" into results `items` field, but in other cases (Object,
79-
-- Scalar) it should be completed into `type` field.
80-
local items = complete_field_to_avro(innerType, {}, subSelections,
81-
context).type
82-
result.type = {
89+
local innerTypeAvro = gql_type_to_avro(innerType, subSelections,
90+
context)
91+
result = {
8392
type = "array",
84-
items = items
93+
items = innerTypeAvro,
8594
}
86-
return result
87-
end
88-
89-
if fieldTypeName == 'Scalar' then
90-
result.type = gql_scalar_to_avro(fieldType)
91-
return result
92-
end
93-
94-
if fieldTypeName == 'Object' then
95-
result.type = object_to_avro(fieldType, subSelections, context)
96-
return result
95+
elseif fieldTypeName == 'Scalar' then
96+
result = gql_scalar_to_avro(fieldType)
97+
elseif fieldTypeName == 'Object' then
98+
result = object_to_avro(fieldType, subSelections, context)
9799
elseif fieldTypeName == 'Interface' or fieldTypeName == 'Union' then
98100
error('Interfaces and Unions are not supported yet')
101+
else
102+
error(string.format('Unknown type "%s"', tostring(fieldTypeName)))
99103
end
100-
error(string.format('Unknown type "%s"', fieldTypeName))
104+
105+
if not isNonNull then
106+
result = make_avro_type_nullable(result)
107+
end
108+
return result
101109
end
102110

103-
--- The function converts a single Object field to avro format
111+
--- The function converts a single Object field to avro format.
104112
local function field_to_avro(object_type, fields, context)
105113
local firstField = fields[1]
106114
assert(#fields == 1, "The aliases are not considered yet")
@@ -109,11 +117,13 @@ local function field_to_avro(object_type, fields, context)
109117
object_type.fields[fieldName]
110118
assert(fieldType ~= nil)
111119
local subSelections = query_util.mergeSelectionSets(fields)
112-
local result = {}
113-
result.name = fieldName
114-
result = complete_field_to_avro(fieldType.kind, result, subSelections,
120+
121+
local fieldTypeAvro = gql_type_to_avro(fieldType.kind, subSelections,
115122
context)
116-
return result
123+
return {
124+
name = fieldName,
125+
type = fieldTypeAvro,
126+
}
117127
end
118128

119129
--- Convert GraphQL object to avro record.
@@ -127,7 +137,7 @@ end
127137
--- of the fields is `namespace_parts` -- table of names of records from the
128138
--- root to the current object
129139
---
130-
--- @treturn table corresponding Avro schema
140+
--- @treturn table `result` is the corresponding Avro schema
131141
object_to_avro = function(object_type, selections, context)
132142
local groupedFieldSet = query_util.collectFields(object_type, selections,
133143
{}, {}, context)
@@ -152,11 +162,11 @@ end
152162
---
153163
--- @tparam table query object which avro schema should be created for
154164
---
155-
--- @treturn table `avro_schema` avro schema for any `query:execute()` result.
165+
--- @treturn table `avro_schema` avro schema for any `query:execute()` result
156166
function query_to_avro.convert(query)
157167
assert(type(query) == "table",
158-
'query should be a table, got: ' .. type(table)
159-
.. '; hint: use ":" instead of "."')
168+
('query should be a table, got: %s; ' ..
169+
'hint: use ":" instead of "."'):format(type(table)))
160170
local state = query.state
161171
local context = query_util.buildContext(state.schema, query.ast, {}, {},
162172
query.operation_name)

test/extra/suite.ini

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
[default]
22
core = app
33
description = tests on features which are not related to specific executor
4-
lua_libs = ../common/lua/test_data_user_order.lua ../testdata/array_and_map_testdata.lua \
4+
lua_libs =
5+
../common/lua/test_data_user_order.lua \
6+
../common/lua/test_data_nested_record.lua \
7+
../testdata/array_and_map_testdata.lua \
58
../testdata/nullable_index_testdata.lua

test/extra/to_avro_nullable.test.lua

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,18 @@ bar:
8181
]]
8282
result_expected = yaml.decode(result_expected)
8383
local result = gql_query:execute(variables)
84-
test:is_deeply(result, result_expected, 'graphql qury exec result')
85-
local ok, ash, r, fs, _
86-
ok, ash = avro.create(avros)
87-
assert(ok)
88-
ok, _ = avro.validate(ash, result)
89-
assert(ok)
84+
test:is_deeply(result, result_expected, 'graphql query exec result')
85+
local ok, ash = avro.create(avros)
86+
assert(ok, tostring(ash))
87+
local ok, err = avro.validate(ash, result)
88+
assert(ok, tostring(err))
9089
test:is(ok, true, 'gql result validation by avro')
91-
ok, fs = avro.compile(ash)
92-
assert(ok)
93-
ok, r = fs.flatten(result)
94-
assert(ok)
95-
ok, r = fs.unflatten(r)
96-
assert(ok)
90+
local ok, fs = avro.compile(ash)
91+
assert(ok, tostring(fs))
92+
local ok, r = fs.flatten(result)
93+
assert(ok, tostring(r))
94+
local ok, r = fs.unflatten(r)
95+
assert(ok, tostring(r))
9796
test:is_deeply(r, result_expected, 'res = unflatten(flatten(res))')
9897

9998
os.exit(test:check() == true and 0 or 1)

0 commit comments

Comments
 (0)