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

Commit e1b9000

Browse files
committed
WIP: Implement breadth-first executor
1 parent 50bb3c4 commit e1b9000

File tree

4 files changed

+251
-16
lines changed

4 files changed

+251
-16
lines changed

graphql/bfs_executor.lua

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
local utils = require('graphql.utils')
2+
local core_util = require('graphql.core.util')
3+
local core_query_util = require('graphql.core.query_util')
4+
5+
local bfs_executor = {}
6+
7+
--- XXX
8+
local function get_argument_type(field_type, argument_name)
9+
for name, argument_type in pairs(field_type.arguments) do
10+
if name == argument_name then
11+
return argument_type
12+
end
13+
end
14+
error(('cannot find argument "%s"'):format(argument_name))
15+
end
16+
17+
local function get_argument_values(field_type, selection, variables)
18+
local args = {}
19+
for _, argument in ipairs(selection.arguments or {}) do
20+
local argument_name = argument.name.value
21+
assert(argument_name ~= nil)
22+
local argument_type = get_argument_type(field_type, argument_name)
23+
local value = core_util.coerceValue(argument.value, argument_type,
24+
variables)
25+
args[argument_name] = value
26+
end
27+
return args
28+
end
29+
30+
local function filter_object(object, object_type, selections, qcontext,
31+
variables)
32+
local required_fields = {}
33+
local selections_per_fields = {}
34+
35+
-- XXX: allow null for object if object_type is nullable (we should not
36+
-- skip NonNull below for an element)
37+
38+
-- XXX: support aliases, fragments, directives
39+
for _, selection in ipairs(selections) do
40+
assert(selection.alias == nil, 'NIY: aliases')
41+
local field_name = selection.name.value
42+
required_fields[field_name] = true
43+
selections_per_fields[field_name] = selection
44+
end
45+
46+
local filtered_object = {}
47+
local fields_info = {}
48+
49+
for field_name, _ in pairs(required_fields) do
50+
local field_type = object_type.fields[field_name]
51+
assert(field_type ~= nil)
52+
53+
if field_type.prepare_resolve then
54+
-- XXX: attend NonNull somehow
55+
-- set deduce inner_type and set is_list
56+
local is_list = false
57+
local inner_type = field_type.kind
58+
while inner_type.ofType ~= nil do
59+
if inner_type.__type == 'List' then
60+
is_list = true
61+
inner_type = inner_type.ofType
62+
elseif inner_type.__type == 'NonNull' then
63+
inner_type = inner_type.ofType
64+
else
65+
error('unknown __type: ' .. tostring(inner_type.__type))
66+
end
67+
end
68+
69+
local selection = selections_per_fields[field_name]
70+
local args = get_argument_values(field_type, selection, variables)
71+
72+
local info = {qcontext = qcontext}
73+
local prepared_resolve = field_type.prepare_resolve(
74+
object, args, info)
75+
fields_info[field_name] = {
76+
is_list = is_list,
77+
kind = inner_type,
78+
prepared_resolve = prepared_resolve,
79+
selections = selection.selectionSet.selections,
80+
}
81+
else
82+
-- XXX: is call to coerceValue needed here?
83+
if object[field_name] ~= nil then
84+
filtered_object[field_name] = object[field_name]
85+
end
86+
end
87+
end
88+
89+
local prepared_object = {
90+
filtered_object = filtered_object,
91+
fields_info = fields_info,
92+
}
93+
return prepared_object
94+
end
95+
96+
local function filter_object_list(object_list, object_type, selections,
97+
qcontext, variables)
98+
local prepared_object_list = {}
99+
100+
for _, object in ipairs(object_list) do
101+
local prepared_object = filter_object(object, object_type, selections,
102+
qcontext, variables)
103+
table.insert(prepared_object_list, prepared_object)
104+
end
105+
106+
return prepared_object_list
107+
end
108+
109+
local function invoke_resolve(prepared_object, qcontext, variables)
110+
local open_set = {}
111+
112+
for field_name, field_info in pairs(prepared_object.fields_info) do
113+
local object_or_list = field_info.prepared_resolve:invoke()
114+
local object_type = field_info.kind
115+
local selections = field_info.selections
116+
117+
-- XXX: maybe we can always process lists and deduce is_list from
118+
-- graphql types
119+
120+
local child
121+
if field_info.is_list then
122+
local child_prepared_object_list = filter_object_list(
123+
object_or_list, object_type, selections, qcontext,
124+
variables)
125+
126+
-- construction
127+
prepared_object.filtered_object[field_name] = {}
128+
for _, child_prepared_object in
129+
ipairs(child_prepared_object_list) do
130+
table.insert(prepared_object.filtered_object[field_name],
131+
child_prepared_object.filtered_object)
132+
end
133+
134+
child = {
135+
prepared_object_list = child_prepared_object_list,
136+
}
137+
else
138+
local child_prepared_object = filter_object(object_or_list,
139+
object_type, selections, qcontext, variables)
140+
141+
-- construction
142+
prepared_object.filtered_object[field_name] =
143+
child_prepared_object.filtered_object
144+
145+
child = {
146+
prepared_object = child_prepared_object,
147+
}
148+
end
149+
150+
table.insert(open_set, child)
151+
end
152+
153+
return open_set
154+
end
155+
156+
local function invoke_resolve_list(prepared_object_list, qcontext, variables)
157+
local open_set = {}
158+
159+
for _, prepared_object in ipairs(prepared_object_list) do
160+
local child_open_set = invoke_resolve(prepared_object, qcontext,
161+
variables)
162+
utils.expand_list(open_set, child_open_set)
163+
end
164+
165+
return open_set
166+
end
167+
168+
--- XXX
169+
function bfs_executor.execute(schema, query_ast, variables, operation_name, opts)
170+
local opts = opts or {}
171+
local qcontext = opts.qcontext
172+
173+
local operation = core_query_util.getOperation(query_ast, operation_name)
174+
local root_object_type = schema[operation.operation]
175+
assert(root_object_type ~= nil,
176+
('cannot find root type for operation "%s"'):format(operation_name))
177+
local root_selections = operation.selectionSet.selections
178+
179+
local root_object = {}
180+
181+
local prepared_root_object = filter_object(
182+
root_object, root_object_type, root_selections, qcontext, variables)
183+
local filtered_root_object = prepared_root_object.filtered_object
184+
local open_set = invoke_resolve(prepared_root_object, qcontext, variables)
185+
186+
-- XXX: don't mix requests to different fields, make all requests for an
187+
-- one field, the for the next: add requests resort stage
188+
189+
while true do
190+
local item = table.remove(open_set, 1)
191+
if item == nil then break end
192+
local child_open_set
193+
if item.prepared_object ~= nil then
194+
child_open_set = invoke_resolve(item.prepared_object, qcontext,
195+
variables)
196+
elseif item.prepared_object_list ~= nil then
197+
child_open_set = invoke_resolve_list(item.prepared_object_list,
198+
qcontext, variables)
199+
else
200+
assert(false,
201+
'cannot find prepared_object nor prepared_object_list')
202+
end
203+
204+
utils.expand_list(open_set, child_open_set)
205+
end
206+
207+
return filtered_root_object
208+
end
209+
210+
return bfs_executor

graphql/core/query_util.lua

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,38 +105,48 @@ function query_util.mergeSelectionSets(fields)
105105
return selections
106106
end
107107

108-
function query_util.buildContext(schema, tree, rootValue, variables, operationName)
109-
local context = {
110-
schema = schema,
111-
rootValue = rootValue,
112-
variables = variables,
113-
operation = nil,
114-
fragmentMap = {},
115-
variableTypes = {},
116-
}
108+
function query_util.getOperation(tree, operationName)
109+
local operation
117110

118111
for _, definition in ipairs(tree.definitions) do
119112
if definition.kind == 'operation' then
120-
if not operationName and context.operation then
113+
if not operationName and operation then
121114
error('Operation name must be specified if more than one operation exists.')
122115
end
123116

124117
if not operationName or definition.name.value == operationName then
125-
context.operation = definition
118+
operation = definition
126119
end
127-
elseif definition.kind == 'fragmentDefinition' then
128-
context.fragmentMap[definition.name.value] = definition
129120
end
130121
end
131122

132-
if not context.operation then
123+
if not operation then
133124
if operationName then
134125
error('Unknown operation "' .. operationName .. '"')
135126
else
136127
error('Must provide an operation')
137128
end
138129
end
139130

131+
return operation
132+
end
133+
134+
function query_util.buildContext(schema, tree, rootValue, variables, operationName)
135+
local context = {
136+
schema = schema,
137+
rootValue = rootValue,
138+
variables = variables,
139+
operation = query_util.getOperation(tree, operationName),
140+
fragmentMap = {},
141+
variableTypes = {},
142+
}
143+
144+
for _, definition in ipairs(tree.definitions) do
145+
if definition.kind == 'fragmentDefinition' then
146+
context.fragmentMap[definition.name.value] = definition
147+
end
148+
end
149+
140150
-- Save variableTypes for the operation.
141151
for _, definition in ipairs(context.operation.variableDefinitions or {}) do
142152
context.variableTypes[definition.variable.name.value] =

graphql/impl.lua

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ local accessor_shard = require('graphql.accessor_shard')
66
local accessor_general = require('graphql.accessor_general')
77
local parse = require('graphql.core.parse')
88
local validate = require('graphql.core.validate')
9-
local execute = require('graphql.core.execute')
9+
--local execute = require('graphql.core.execute') -- XXX
10+
local bfs_executor = require('graphql.bfs_executor')
1011
local query_to_avro = require('graphql.query_to_avro')
1112
local simple_config = require('graphql.simple_config')
1213
local config_complement = require('graphql.config_complement')
@@ -41,15 +42,19 @@ local function gql_execute(qstate, variables, operation_name)
4142
check(operation_name, 'operation_name', 'string', 'nil')
4243

4344
assert(qstate.query_settings)
44-
local root_value = {}
45+
-- local root_value = {} -- XXX
4546
local qcontext = {
4647
query_settings = qstate.query_settings,
4748
}
4849

4950
local traceback
5051
local ok, data = xpcall(function()
52+
--[[
5153
return execute(state.schema, qstate.ast, root_value, variables,
5254
operation_name, {qcontext = qcontext})
55+
]]--
56+
return bfs_executor.execute(state.schema, qstate.ast, variables,
57+
operation_name, {qcontext = qcontext})
5358
end, function(err)
5459
traceback = debug.traceback()
5560
return err

graphql/utils.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,14 @@ function utils.serialize_error(err, traceback)
276276
return res
277277
end
278278

279+
--- Append all elements of the list `tail` to the end of list `list`.
280+
---
281+
--- @tparam table list list to add elements to
282+
--- @tparam table tail list to add elements from
283+
function utils.expand_list(list, tail)
284+
for _, item in ipairs(tail) do
285+
table.insert(list, item)
286+
end
287+
end
288+
279289
return utils

0 commit comments

Comments
 (0)