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

Commit e47af16

Browse files
committed
Move general parts of executor to query_util.lua
This patch is important for graphql query -> avro schema convertor implementation, because both executor and convertor walks over query AST. Part of #7
1 parent b771c87 commit e47af16

File tree

2 files changed

+152
-82
lines changed

2 files changed

+152
-82
lines changed

graphql/core/execute.lua

+7-82
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ local path = (...):gsub('%.[^%.]+$', '')
22
local types = require(path .. '.types')
33
local util = require(path .. '.util')
44
local introspection = require(path .. '.introspection')
5+
local query_util = require(path .. '.query_util')
56

67
local function typeFromAST(node, schema)
78
local innerType
@@ -63,89 +64,10 @@ local function doesFragmentApply(fragment, type, context)
6364
end
6465
end
6566

66-
local function mergeSelectionSets(fields)
67-
local selections = {}
68-
69-
for i = 1, #fields do
70-
local selectionSet = fields[i].selectionSet
71-
if selectionSet then
72-
for j = 1, #selectionSet.selections do
73-
table.insert(selections, selectionSet.selections[j])
74-
end
75-
end
76-
end
77-
78-
return selections
79-
end
80-
8167
local function defaultResolver(object, arguments, info)
8268
return object[info.fieldASTs[1].name.value]
8369
end
8470

85-
local function buildContext(schema, tree, rootValue, variables, operationName)
86-
local context = {
87-
schema = schema,
88-
rootValue = rootValue,
89-
variables = variables,
90-
operation = nil,
91-
fragmentMap = {},
92-
-- The field is passed to resolve function within info attribute.
93-
-- Can be used to store any data within one query.
94-
qcontext = {}
95-
}
96-
97-
for _, definition in ipairs(tree.definitions) do
98-
if definition.kind == 'operation' then
99-
if not operationName and context.operation then
100-
error('Operation name must be specified if more than one operation exists.')
101-
end
102-
103-
if not operationName or definition.name.value == operationName then
104-
context.operation = definition
105-
end
106-
elseif definition.kind == 'fragmentDefinition' then
107-
context.fragmentMap[definition.name.value] = definition
108-
end
109-
end
110-
111-
if not context.operation then
112-
if operationName then
113-
error('Unknown operation "' .. operationName .. '"')
114-
else
115-
error('Must provide an operation')
116-
end
117-
end
118-
119-
return context
120-
end
121-
122-
local function collectFields(objectType, selections, visitedFragments, result, context)
123-
for _, selection in ipairs(selections) do
124-
if selection.kind == 'field' then
125-
if shouldIncludeNode(selection, context) then
126-
local name = getFieldResponseKey(selection)
127-
result[name] = result[name] or {}
128-
table.insert(result[name], selection)
129-
end
130-
elseif selection.kind == 'inlineFragment' then
131-
if shouldIncludeNode(selection, context) and doesFragmentApply(selection, objectType, context) then
132-
collectFields(objectType, selection.selectionSet.selections, visitedFragments, result, context)
133-
end
134-
elseif selection.kind == 'fragmentSpread' then
135-
local fragmentName = selection.name.value
136-
if shouldIncludeNode(selection, context) and not visitedFragments[fragmentName] then
137-
visitedFragments[fragmentName] = true
138-
local fragment = context.fragmentMap[fragmentName]
139-
if fragment and shouldIncludeNode(fragment, context) and doesFragmentApply(fragment, objectType, context) then
140-
collectFields(objectType, fragment.selectionSet.selections, visitedFragments, result, context)
141-
end
142-
end
143-
end
144-
end
145-
146-
return result
147-
end
148-
14971
local evaluateSelections
15072

15173
local function completeValue(fieldType, result, subSelections, context)
@@ -230,21 +152,24 @@ local function getFieldEntry(objectType, object, fields, context)
230152
}
231153

232154
local resolvedObject = (fieldType.resolve or defaultResolver)(object, arguments, info)
233-
local subSelections = mergeSelectionSets(fields)
155+
local subSelections = query_util.mergeSelectionSets(fields)
234156

235157
return completeValue(fieldType.kind, resolvedObject, subSelections, context)
236158
end
237159

238160
evaluateSelections = function(objectType, object, selections, context)
239-
local groupedFieldSet = collectFields(objectType, selections, {}, {}, context)
161+
local groupedFieldSet = query_util.collectFields(objectType, selections, {}, {}, context)
240162

241163
return util.map(groupedFieldSet, function(fields)
242164
return getFieldEntry(objectType, object, fields, context)
243165
end)
244166
end
245167

246168
return function(schema, tree, rootValue, variables, operationName)
247-
local context = buildContext(schema, tree, rootValue, variables, operationName)
169+
local context = query_util.buildContext(schema, tree, rootValue, variables, operationName)
170+
-- The field is passed to resolve function within info attribute.
171+
-- Can be used to store any data within one query.
172+
context.qcontext = {}
248173
local rootType = schema[context.operation.operation]
249174

250175
if not rootType then

graphql/core/query_util.lua

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
local path = (...):gsub('%.[^%.]+$', '')
2+
local types = require(path .. '.types')
3+
local util = require(path .. '.util')
4+
5+
local query_util = {}
6+
7+
local function typeFromAST(node, schema)
8+
local innerType
9+
if node.kind == 'listType' then
10+
innerType = typeFromAST(node.type)
11+
return innerType and types.list(innerType)
12+
elseif node.kind == 'nonNullType' then
13+
innerType = typeFromAST(node.type)
14+
return innerType and types.nonNull(innerType)
15+
else
16+
assert(node.kind == 'namedType', 'Variable must be a named type')
17+
return schema:getType(node.name.value)
18+
end
19+
end
20+
21+
local function getFieldResponseKey(field)
22+
return field.alias and field.alias.name.value or field.name.value
23+
end
24+
25+
local function shouldIncludeNode(selection, context)
26+
if selection.directives then
27+
local function isDirectiveActive(key, _type)
28+
local directive = util.find(selection.directives, function(directive)
29+
return directive.name.value == key
30+
end)
31+
32+
if not directive then return end
33+
34+
local ifArgument = util.find(directive.arguments, function(argument)
35+
return argument.name.value == 'if'
36+
end)
37+
38+
if not ifArgument then return end
39+
40+
return util.coerceValue(ifArgument.value, _type.arguments['if'], context.variables)
41+
end
42+
43+
if isDirectiveActive('skip', types.skip) then return false end
44+
if isDirectiveActive('include', types.include) == false then return false end
45+
end
46+
47+
return true
48+
end
49+
50+
local function doesFragmentApply(fragment, type, context)
51+
if not fragment.typeCondition then return true end
52+
53+
local innerType = typeFromAST(fragment.typeCondition, context.schema)
54+
55+
if innerType == type then
56+
return true
57+
elseif innerType.__type == 'Interface' then
58+
local implementors = context.schema:getImplementors(innerType.name)
59+
return implementors and implementors[type]
60+
elseif innerType.__type == 'Union' then
61+
return util.find(innerType.types, function(member)
62+
return member == type
63+
end)
64+
end
65+
end
66+
67+
function query_util.collectFields(objectType, selections, visitedFragments, result, context)
68+
for _, selection in ipairs(selections) do
69+
if selection.kind == 'field' then
70+
if shouldIncludeNode(selection, context) then
71+
local name = getFieldResponseKey(selection)
72+
result[name] = result[name] or {}
73+
table.insert(result[name], selection)
74+
end
75+
elseif selection.kind == 'inlineFragment' then
76+
if shouldIncludeNode(selection, context) and doesFragmentApply(selection, objectType, context) then
77+
collectFields(objectType, selection.selectionSet.selections, visitedFragments, result, context)
78+
end
79+
elseif selection.kind == 'fragmentSpread' then
80+
local fragmentName = selection.name.value
81+
if shouldIncludeNode(selection, context) and not visitedFragments[fragmentName] then
82+
visitedFragments[fragmentName] = true
83+
local fragment = context.fragmentMap[fragmentName]
84+
if fragment and shouldIncludeNode(fragment, context) and doesFragmentApply(fragment, objectType, context) then
85+
collectFields(objectType, fragment.selectionSet.selections, visitedFragments, result, context)
86+
end
87+
end
88+
end
89+
end
90+
91+
return result
92+
end
93+
94+
95+
function query_util.mergeSelectionSets(fields)
96+
local selections = {}
97+
98+
for i = 1, #fields do
99+
local selectionSet = fields[i].selectionSet
100+
if selectionSet then
101+
for j = 1, #selectionSet.selections do
102+
table.insert(selections, selectionSet.selections[j])
103+
end
104+
end
105+
end
106+
107+
return selections
108+
end
109+
110+
111+
function query_util.buildContext(schema, tree, rootValue, variables, operationName)
112+
local context = {
113+
schema = schema,
114+
rootValue = rootValue,
115+
variables = variables,
116+
operation = nil,
117+
fragmentMap = {}
118+
}
119+
120+
for _, definition in ipairs(tree.definitions) do
121+
if definition.kind == 'operation' then
122+
if not operationName and context.operation then
123+
error('Operation name must be specified if more than one operation exists.')
124+
end
125+
126+
if not operationName or definition.name.value == operationName then
127+
context.operation = definition
128+
end
129+
elseif definition.kind == 'fragmentDefinition' then
130+
context.fragmentMap[definition.name.value] = definition
131+
end
132+
end
133+
134+
if not context.operation then
135+
if operationName then
136+
error('Unknown operation "' .. operationName .. '"')
137+
else
138+
error('Must provide an operation')
139+
end
140+
end
141+
142+
return context
143+
end
144+
145+
return query_util

0 commit comments

Comments
 (0)