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

Compile a query with several operations #172

Merged
merged 1 commit into from
Jun 8, 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
22 changes: 13 additions & 9 deletions graphql/query_to_avro.lua
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,22 @@ object_to_avro = function(object_type, selections, context)
return result
end

--- Create an Avro schema for a given query.
--- Create an Avro schema for a given query / operation.
---
--- @tparam table query object which avro schema should be created for
--- @tparam table qstate compiled query for which the avro schema should be
--- created
---
--- @treturn table `avro_schema` avro schema for any `query:execute()` result
function query_to_avro.convert(query)
assert(type(query) == "table",
('query should be a table, got: %s; ' ..
--- @tparam[opt] string operation_name optional operation name
---
--- @treturn table `avro_schema` avro schema for any
--- `qstate:execute(..., operation_name)` result
function query_to_avro.convert(qstate, operation_name)
assert(type(qstate) == "table",
('qstate should be a table, got: %s; ' ..
'hint: use ":" instead of "."'):format(type(table)))
local state = query.state
local context = query_util.buildContext(state.schema, query.ast, {}, {},
query.operation_name)
local state = qstate.state
local context = query_util.buildContext(state.schema, qstate.ast, {}, {},
operation_name)
-- The variable is necessary to avoid fullname interferention.
-- Each nested Avro record creates it's namespace.
context.namespace_parts = {}
Expand Down
9 changes: 8 additions & 1 deletion graphql/server/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,14 @@ function server.init(graphql, host, port)
}
end

local ok, result = pcall(compiled_query.execute, compiled_query, variables)
local operation_name = parsed.operationName
-- box.NULL -> nil
if operation_name == nil then
operation_name = nil
end

local ok, result = pcall(compiled_query.execute, compiled_query,
variables, operation_name)
if not ok then
return {
status = 200,
Expand Down
63 changes: 33 additions & 30 deletions graphql/tarantool_graphql.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1617,64 +1617,67 @@ local function parse_cfg(cfg)
return state
end

--- The function just makes some reasonable assertions on input
--- and then call graphql-lua execute.
local function gql_execute(qstate, variables)
--- Execute an operation from compiled query.
---
--- @tparam qstate compiled query
---
--- @tparam variables variables to pass to the query
---
--- @tparam[opt] string operation_name optional operation name
---
--- @treturn table result of the operation
local function gql_execute(qstate, variables, operation_name)
assert(qstate.state)
local state = qstate.state
assert(state.schema)

assert(type(variables) == 'table', 'variables must be table, got ' ..
type(variables))
check(variables, 'variables', 'table')
check(operation_name, 'operation_name', 'string', 'nil')

local root_value = {}
local operation_name = qstate.operation_name
assert(type(operation_name) == 'string',
'operation_name must be a string, got ' .. type(operation_name))

return execute(state.schema, qstate.ast, root_value, variables,
operation_name)
end

local function compile_and_execute(state, query, variables)
--- Compile a query and execute an operation.
---
--- See @{gql_compile} and @{gql_execute} for parameters description.
---
--- @treturn table result of the operation
local function compile_and_execute(state, query, variables, operation_name)
assert(type(state) == 'table', 'use :gql_execute(...) instead of ' ..
'.execute(...)')
assert(state.schema ~= nil, 'have not compiled schema')
check(query, 'query', 'string')
check(variables, 'variables', 'table', 'nil')
check(operation_name, 'operation_name', 'string', 'nil')

local compiled_query = state:compile(query)
return compiled_query:execute(variables)
return compiled_query:execute(variables, operation_name)
end

--- The function parses a query string, validate the resulting query
--- against the GraphQL schema and provides an object with the function to
--- execute the query with specific variables values.
--- Parse GraphQL query string, validate against the GraphQL schema and
--- provide an object with the function to execute an operation from the
--- request with specific variables values.
---
--- @tparam table state current state of graphql, including
--- schemas, collections and accessor
--- @tparam string query query string
--- @tparam table state a tarantool_graphql instance
---
--- @tparam string query text of a GraphQL query
---
--- @treturn table compiled query with `execute` and `avro_schema` functions
local function gql_compile(state, query)
assert(type(state) == 'table' and type(query) == 'string',
'use :validate(...) instead of .validate(...)')
assert(state.schema ~= nil, 'have not compiled schema')
check(query, 'query', 'string')

local ast = parse(query)

local operation_name
for _, definition in pairs(ast.definitions) do
if definition.kind == 'operation' then
operation_name = definition.name.value
end
end

assert(operation_name, "there is no 'operation' in query " ..
"definitions:\n" .. yaml.encode(ast))

validate(state.schema, ast)

local qstate = {
state = state,
ast = ast,
operation_name = operation_name,
}

local gql_query = setmetatable(qstate, {
Expand Down Expand Up @@ -1719,11 +1722,11 @@ function tarantool_graphql.compile(query)
return default_instance:compile(query)
end

function tarantool_graphql.execute(query, variables)
function tarantool_graphql.execute(query, variables, operation_name)
if default_instance == nil then
default_instance = tarantool_graphql.new()
end
return default_instance:execute(query, variables)
return default_instance:execute(query, variables, operation_name)
end

function tarantool_graphql.start_server()
Expand Down
107 changes: 105 additions & 2 deletions test/testdata/common_testdata.lua
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ end

function common_testdata.run_queries(gql_wrapper)
local test = tap.test('common')
test:plan(18)
test:plan(24)

local query_1 = [[
query user_by_order($order_id: String) {
Expand All @@ -338,13 +338,116 @@ function common_testdata.run_queries(gql_wrapper)
first_name: Ivan
]]):strip())

local variables_1 = {order_id = 'order_id_1'}

utils.show_trace(function()
local variables_1 = {order_id = 'order_id_1'}
local gql_query_1 = gql_wrapper:compile(query_1)
local result = gql_query_1:execute(variables_1)
test:is_deeply(result, exp_result_1, '1')
end)

local query_1n = [[
query($order_id: String) {
order_collection(order_id: $order_id) {
order_id
description
user_connection {
user_id
last_name
first_name
}
}
}
]]

utils.show_trace(function()
local gql_query_1n = gql_wrapper:compile(query_1n)
local result = gql_query_1n:execute(variables_1)
test:is_deeply(result, exp_result_1, '1n')
end)

local query_1inn = [[
{
order_collection(order_id: "order_id_1") {
order_id
description
user_connection {
user_id
last_name
first_name
}
}
}
]]

utils.show_trace(function()
local gql_query_1inn = gql_wrapper:compile(query_1inn)
local result = gql_query_1inn:execute({})
test:is_deeply(result, exp_result_1, '1inn')
end)

local query_1tn = [[
query get_order {
order_collection(order_id: "order_id_1") {
order_id
description
}
}
query {
order_collection(order_id: "order_id_1") {
order_id
description
}
}
]]

local err_exp = 'Cannot have more than one operation when using ' ..
'anonymous operations'
local ok, err = pcall(gql_wrapper.compile, gql_wrapper, query_1tn)
test:is_deeply({ok, test_utils.strip_error(err)}, {false, err_exp},
'unnamed query should be a single one')

local query_1t = [[
query user_by_order {
order_collection(order_id: "order_id_1") {
order_id
description
user_connection {
user_id
last_name
first_name
}
}
}
query get_order {
order_collection(order_id: "order_id_1") {
order_id
description
}
}
]]

local gql_query_1t = utils.show_trace(function()
return gql_wrapper:compile(query_1t)
end)

local err_exp = 'Operation name must be specified if more than one ' ..
'operation exists.'
local ok, err = pcall(gql_query_1t.execute, gql_query_1t, {})
test:is_deeply({ok, test_utils.strip_error(err)}, {false, err_exp},
'non-determined query name should give an error')

local err_exp = 'Unknown operation "non_existent_operation"'
local ok, err = pcall(gql_query_1t.execute, gql_query_1t, {},
'non_existent_operation')
test:is_deeply({ok, test_utils.strip_error(err)}, {false, err_exp},
'wrong operation name should give an error')

utils.show_trace(function()
local result = gql_query_1t:execute({}, 'user_by_order')
test:is_deeply(result, exp_result_1, 'execute an operation by name')
end)

local query_2 = [[
query user_order($user_id: String, $first_name: String, $limit: Int,
$offset: String) {
Expand Down