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

Commit 601ca51

Browse files
committed
WIP: Integrate expressions into GraphQL
Part of #13.
1 parent 9fe2f4d commit 601ca51

File tree

5 files changed

+201
-2
lines changed

5 files changed

+201
-2
lines changed

graphql/core/rules.lua

+46
Original file line numberDiff line numberDiff line change
@@ -604,4 +604,50 @@ end
604604

605605
-- }}}
606606

607+
-- {{{ compile filter argument
608+
609+
-- XXX: explicitly forbid filters passed by variables? Or compile it
610+
-- afterwards?
611+
612+
function rules.compileFilterArgument(node, context)
613+
local expressions = require('graphql.expressions') -- XXX: move upward
614+
615+
assert(node.kind == 'argument')
616+
local argument_node = node
617+
local argument_name = argument_node.name.value
618+
619+
-- XXX: 1:1-connected object can have field 'filter'
620+
if argument_name ~= 'filter' then return end
621+
622+
-- save compiled expression
623+
local value_node = argument_node.value
624+
assert(value_node.kind == 'string') -- XXX: better error
625+
local string_node = value_node
626+
local value = string_node.value
627+
assert(type(value) == 'string') -- XXX: better error
628+
argument_node.compiled = expressions.new(value)
629+
630+
-- XXX: don't blindly find the pattern {kind = 'variable', ...}, but either
631+
-- traverse a tree according to node kinds or export used variables info from
632+
-- expressions module
633+
634+
-- mark used variables
635+
local open_set = {argument_node.compiled}
636+
while true do
637+
local e_node = table.remove(open_set, 1)
638+
if e_node == nil then break end
639+
if type(e_node) == 'table' then
640+
if e_node.kind == 'variable' then
641+
context.variableReferences[e_node.name] = true
642+
else
643+
for _, child_e_node in pairs(e_node) do
644+
table.insert(open_set, child_e_node)
645+
end
646+
end
647+
end
648+
end
649+
end
650+
651+
-- }}}
652+
607653
return rules

graphql/core/util.lua

+1
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ function util.coerceValue(node, schemaType, variables, opts)
176176
end
177177

178178
if schemaType.__type == 'Scalar' then
179+
-- XXX: if a string node and have .compiled
179180
if schemaType.parseLiteral(node) == nil then
180181
error(e.wrong_value(('Could not coerce "%s" to "%s"'):format(
181182
tostring(node.value), schemaType.name)))

graphql/core/validate.lua

+4-1
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,10 @@ local visitors = {
409409
end)
410410
end,
411411

412-
rules = { rules.uniqueInputObjectFields }
412+
rules = {
413+
rules.uniqueInputObjectFields,
414+
rules.compileFilterArgument,
415+
}
413416
},
414417

415418
-- <inputObject>

graphql/gen_arguments.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ function gen_arguments.list_args(db_schema, collection_name)
348348
return {
349349
{name = 'limit', type = 'int*'},
350350
{name = 'offset', type = offset_type},
351-
-- {name = 'filter', type = ...},
351+
{name = 'filter', type = 'string*'},
352352
pcre_field,
353353
}
354354
end

test/common/expressions.test.lua

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env tarantool
2+
3+
-- https://github.com/tarantool/graphql/issues/13
4+
5+
local fio = require('fio')
6+
7+
-- require in-repo version of graphql/ sources despite current working directory
8+
package.path = fio.abspath(debug.getinfo(1).source:match("@?(.*/)")
9+
:gsub('/./', '/'):gsub('/+$', '')) .. '/../../?.lua' .. ';' .. package.path
10+
11+
local tap = require('tap')
12+
local yaml = require('yaml')
13+
local test_utils = require('test.test_utils')
14+
local testdata = require('test.testdata.common_testdata')
15+
16+
local function run_queries(gql_wrapper)
17+
local test = tap.test('expressions')
18+
test:plan(3)
19+
20+
local exp_result_1_and_2 = test_utils.deeply_number_tostring(yaml.decode(([[
21+
---
22+
order_collection:
23+
- order_id: order_id_4
24+
price: 4.3333333333333
25+
- order_id: order_id_5
26+
price: 4.6666666666667
27+
]]):strip()))
28+
29+
-- {{{ test expresion (use variables and &&)
30+
31+
local query_1 = [[
32+
query get_by_expr($order_id_from: Double, $order_id_to: Double) {
33+
order_collection(
34+
filter: "order_id >= $order_id_from && order_id < $order_id_to"
35+
) {
36+
order_id
37+
price
38+
}
39+
}
40+
]]
41+
42+
local gql_query_1 = test_utils.show_trace(function()
43+
return gql_wrapper:compile(query_1)
44+
end)
45+
46+
local variables_1 = {
47+
order_id_from = 4,
48+
order_id_to = 5,
49+
}
50+
51+
local result_1 = test_utils.show_trace(function()
52+
return gql_query_1:execute(variables_1)
53+
end)
54+
55+
local result_1_data = test_utils.deeply_number_tostring(result_1.data)
56+
test:is_deeply(result_1_data, exp_result_1_and_2,
57+
'expressions with variables and &&')
58+
59+
-- }}}
60+
61+
-- {{{ test expresion (use immediate values and &&)
62+
63+
local query_2 = [[
64+
query get_by_expr {
65+
order_collection(
66+
filter: "order_id >= 4 && order_id < 5"
67+
) {
68+
order_id
69+
price
70+
}
71+
}
72+
]]
73+
74+
local gql_query_2 = test_utils.show_trace(function()
75+
return gql_wrapper:compile(query_2)
76+
end)
77+
78+
local variables_2 = {}
79+
80+
local result_2 = test_utils.show_trace(function()
81+
return gql_query_2:execute(variables_2)
82+
end)
83+
84+
local result_2_data = test_utils.deeply_number_tostring(result_2.data)
85+
test:is_deeply(result_2_data, exp_result_1_and_2,
86+
'expression with immediate values and &&')
87+
88+
-- }}}
89+
90+
-- {{{ test expression with nested fields and ||
91+
92+
local query_3 = [[
93+
query get_by_expr {
94+
order_metainfo_collection(
95+
filter: "store.address.city == 1 || store.address.city == 42"
96+
) {
97+
order_metainfo_id
98+
store {
99+
address {
100+
city
101+
}
102+
}
103+
}
104+
}
105+
]]
106+
107+
local gql_query_3 = test_utils.show_trace(function()
108+
return gql_wrapper:compile(query_3)
109+
end)
110+
111+
local exp_result_3 = test_utils.deeply_number_tostring(yaml.decode(([[
112+
---
113+
order_metainfo_collection:
114+
- order_metainfo_id: order_metainfo_id_1
115+
store:
116+
address:
117+
city: city 1
118+
- order_metainfo_id: order_metainfo_id_42
119+
store:
120+
address:
121+
city: city 42
122+
]]):strip()))
123+
124+
local variables_3 = {}
125+
126+
local result_3 = test_utils.show_trace(function()
127+
return gql_query_3:execute(variables_3)
128+
end)
129+
130+
local result_3_data = test_utils.deeply_number_tostring(result_3.data)
131+
test:is_deeply(result_3_data, exp_result_3,
132+
'expression with nested fields and ||')
133+
134+
-- }}}
135+
136+
assert(test:check(), 'check plan')
137+
end
138+
139+
box.cfg({})
140+
141+
test_utils.run_testdata(testdata, {
142+
run_queries = run_queries,
143+
graphql_opts = {
144+
-- gh-137: timeout exceeded
145+
timeout_ms = 10000, -- 10 seconds
146+
}
147+
})
148+
149+
os.exit()

0 commit comments

Comments
 (0)