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

Kh/to avro #58

Merged
merged 4 commits into from
Mar 13, 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
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ default:

.PHONY: lint
lint:
luacheck graphql/*.lua test/local/*.lua test/testdata/*.lua \
luacheck graphql/*.lua \
test/local/*.lua \
test/testdata/*.lua \
test/common/*.test.lua test/common/lua/*.lua \
test/extra/*.test.lua \
--no-redefined --no-unused-args

.PHONY: test
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,18 @@ make test

## Requirements

* For use: tarantool, lulpeg, >=tarantool/shard-1.1-91-gfa88bf8 (optional),
tarantool/avro-schema.
* For test (additionally to 'for use'): python 2.7, virtualenv, luacheck,
>=tarantool/shard-1.1-92-gec1a27e.
* For building apidoc (additionally to 'for use'): ldoc.
* For use:
* tarantool,
* lulpeg,
* >=tarantool/avro-schema-2.0-71-gfea0ead,
* >=tarantool/shard-1.1-91-gfa88bf8 (optional).
* For test (additionally to 'for use'):
* python 2.7,
* virtualenv,
* luacheck,
* >=tarantool/shard-1.1-92-gec1a27e.
* For building apidoc (additionally to 'for use'):
* ldoc.

## License

Expand Down
89 changes: 7 additions & 82 deletions graphql/core/execute.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local path = (...):gsub('%.[^%.]+$', '')
local types = require(path .. '.types')
local util = require(path .. '.util')
local introspection = require(path .. '.introspection')
local query_util = require(path .. '.query_util')

local function typeFromAST(node, schema)
local innerType
Expand Down Expand Up @@ -63,89 +64,10 @@ local function doesFragmentApply(fragment, type, context)
end
end

local function mergeSelectionSets(fields)
local selections = {}

for i = 1, #fields do
local selectionSet = fields[i].selectionSet
if selectionSet then
for j = 1, #selectionSet.selections do
table.insert(selections, selectionSet.selections[j])
end
end
end

return selections
end

local function defaultResolver(object, arguments, info)
return object[info.fieldASTs[1].name.value]
end

local function buildContext(schema, tree, rootValue, variables, operationName)
local context = {
schema = schema,
rootValue = rootValue,
variables = variables,
operation = nil,
fragmentMap = {},
-- The field is passed to resolve function within info attribute.
-- Can be used to store any data within one query.
qcontext = {}
}

for _, definition in ipairs(tree.definitions) do
if definition.kind == 'operation' then
if not operationName and context.operation then
error('Operation name must be specified if more than one operation exists.')
end

if not operationName or definition.name.value == operationName then
context.operation = definition
end
elseif definition.kind == 'fragmentDefinition' then
context.fragmentMap[definition.name.value] = definition
end
end

if not context.operation then
if operationName then
error('Unknown operation "' .. operationName .. '"')
else
error('Must provide an operation')
end
end

return context
end

local function collectFields(objectType, selections, visitedFragments, result, context)
for _, selection in ipairs(selections) do
if selection.kind == 'field' then
if shouldIncludeNode(selection, context) then
local name = getFieldResponseKey(selection)
result[name] = result[name] or {}
table.insert(result[name], selection)
end
elseif selection.kind == 'inlineFragment' then
if shouldIncludeNode(selection, context) and doesFragmentApply(selection, objectType, context) then
collectFields(objectType, selection.selectionSet.selections, visitedFragments, result, context)
end
elseif selection.kind == 'fragmentSpread' then
local fragmentName = selection.name.value
if shouldIncludeNode(selection, context) and not visitedFragments[fragmentName] then
visitedFragments[fragmentName] = true
local fragment = context.fragmentMap[fragmentName]
if fragment and shouldIncludeNode(fragment, context) and doesFragmentApply(fragment, objectType, context) then
collectFields(objectType, fragment.selectionSet.selections, visitedFragments, result, context)
end
end
end
end

return result
end

local evaluateSelections

local function completeValue(fieldType, result, subSelections, context)
Expand Down Expand Up @@ -230,21 +152,24 @@ local function getFieldEntry(objectType, object, fields, context)
}

local resolvedObject = (fieldType.resolve or defaultResolver)(object, arguments, info)
local subSelections = mergeSelectionSets(fields)
local subSelections = query_util.mergeSelectionSets(fields)

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

evaluateSelections = function(objectType, object, selections, context)
local groupedFieldSet = collectFields(objectType, selections, {}, {}, context)
local groupedFieldSet = query_util.collectFields(objectType, selections, {}, {}, context)

return util.map(groupedFieldSet, function(fields)
return getFieldEntry(objectType, object, fields, context)
end)
end

return function(schema, tree, rootValue, variables, operationName)
local context = buildContext(schema, tree, rootValue, variables, operationName)
local context = query_util.buildContext(schema, tree, rootValue, variables, operationName)
-- The field is passed to resolve function within info attribute.
-- Can be used to store any data within one query.
context.qcontext = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you moved it from buildContext? The qcontext field looks as part of context to build up for me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it just because buildContext since now is uset in to_avro too. And it is not necessary in to_avro. Second, I suppose that we need to pass this variable from tarantool_graphql (compile or/and execute), for example to specify query timeout. What do you think?

local rootType = schema[context.operation.operation]

if not rootType then
Expand Down
143 changes: 143 additions & 0 deletions graphql/core/query_util.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
local path = (...):gsub('%.[^%.]+$', '')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that you more or less investigated this functions. It would be glab to see brief comments for them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have just copied). I will do further investigations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, (...) stores the string passed to require when the query_util file was required.
gsub:

  1. '%.' means just match '.'
  2. '%.[^%.]+$' means match everything since the last '.'
  3. the whole string just creates path variable which simply stores dot-delimited path to the query_util file relative to the file query_util was required from.

local types = require(path .. '.types')
local util = require(path .. '.util')

local query_util = {}

local function typeFromAST(node, schema)
local innerType
if node.kind == 'listType' then
innerType = typeFromAST(node.type)
return innerType and types.list(innerType)
elseif node.kind == 'nonNullType' then
innerType = typeFromAST(node.type)
return innerType and types.nonNull(innerType)
else
assert(node.kind == 'namedType', 'Variable must be a named type')
return schema:getType(node.name.value)
end
end

local function getFieldResponseKey(field)
return field.alias and field.alias.name.value or field.name.value
end

local function shouldIncludeNode(selection, context)
if selection.directives then
local function isDirectiveActive(key, _type)
local directive = util.find(selection.directives, function(directive)
return directive.name.value == key
end)

if not directive then return end

local ifArgument = util.find(directive.arguments, function(argument)
return argument.name.value == 'if'
end)

if not ifArgument then return end

return util.coerceValue(ifArgument.value, _type.arguments['if'], context.variables)
end

if isDirectiveActive('skip', types.skip) then return false end
if isDirectiveActive('include', types.include) == false then return false end
end

return true
end

local function doesFragmentApply(fragment, type, context)
if not fragment.typeCondition then return true end

local innerType = typeFromAST(fragment.typeCondition, context.schema)

if innerType == type then
return true
elseif innerType.__type == 'Interface' then
local implementors = context.schema:getImplementors(innerType.name)
return implementors and implementors[type]
elseif innerType.__type == 'Union' then
return util.find(innerType.types, function(member)
return member == type
end)
end
end

function query_util.collectFields(objectType, selections, visitedFragments, result, context)
for _, selection in ipairs(selections) do
if selection.kind == 'field' then
if shouldIncludeNode(selection, context) then
local name = getFieldResponseKey(selection)
result[name] = result[name] or {}
table.insert(result[name], selection)
end
elseif selection.kind == 'inlineFragment' then
if shouldIncludeNode(selection, context) and doesFragmentApply(selection, objectType, context) then
collectFields(objectType, selection.selectionSet.selections, visitedFragments, result, context)
end
elseif selection.kind == 'fragmentSpread' then
local fragmentName = selection.name.value
if shouldIncludeNode(selection, context) and not visitedFragments[fragmentName] then
visitedFragments[fragmentName] = true
local fragment = context.fragmentMap[fragmentName]
if fragment and shouldIncludeNode(fragment, context) and doesFragmentApply(fragment, objectType, context) then
collectFields(objectType, fragment.selectionSet.selections, visitedFragments, result, context)
end
end
end
end

return result
end

function query_util.mergeSelectionSets(fields)
local selections = {}

for i = 1, #fields do
local selectionSet = fields[i].selectionSet
if selectionSet then
for j = 1, #selectionSet.selections do
table.insert(selections, selectionSet.selections[j])
end
end
end

return selections
end

function query_util.buildContext(schema, tree, rootValue, variables, operationName)
local context = {
schema = schema,
rootValue = rootValue,
variables = variables,
operation = nil,
fragmentMap = {}
}

for _, definition in ipairs(tree.definitions) do
if definition.kind == 'operation' then
if not operationName and context.operation then
error('Operation name must be specified if more than one operation exists.')
end

if not operationName or definition.name.value == operationName then
context.operation = definition
end
elseif definition.kind == 'fragmentDefinition' then
context.fragmentMap[definition.name.value] = definition
end
end

if not context.operation then
if operationName then
error('Unknown operation "' .. operationName .. '"')
else
error('Must provide an operation')
end
end

return context
end

return query_util
Loading