Skip to content

Commit

Permalink
graphql: Implement reflection of ObjectTypes.
Browse files Browse the repository at this point in the history
ObjectTypes get reflected as type INTERFACE into GraphQL. Additionally,
non-abstract ObjectTypes get reflected as type OBJECT, but with the
"Type" appended at the end of their name.

This allows the return type of fields/functions to be specified in terms
of INTERFACE types and the actual object __typename to reflect the
OBJECT type of the specific instance.
  • Loading branch information
vpetrovykh committed Jun 27, 2018
1 parent d920d57 commit 0d8d5a7
Show file tree
Hide file tree
Showing 4 changed files with 395 additions and 182 deletions.
31 changes: 9 additions & 22 deletions edb/lang/graphql/translator.py
Expand Up @@ -33,8 +33,7 @@


class GraphQLTranslatorContext:
def __init__(self, *, schema, gqlcore, variables, operation_name, query,
modules):
def __init__(self, *, schema, gqlcore, variables, operation_name, query):
self.schema = schema
self.variables = variables
self.operation_name = operation_name
Expand All @@ -44,11 +43,9 @@ def __init__(self, *, schema, gqlcore, variables, operation_name, query,
self.fields = []
self.path = []
self.include_base = [False]
self.gql_schema = gt.Schema(schema, modules)
self.gqlcore = gqlcore
self.gql_schema = gt.Schema(gqlcore)
self.gqlcore_schema = gqlcore._gql_schema
self.query = query
self.modules = list(modules)
self.modules.sort()


Step = namedtuple('Step', ['name', 'type'])
Expand Down Expand Up @@ -96,7 +93,7 @@ def visit_Document(self, node):
}

gqlresult = gql_proc(
self._context.gqlcore,
self._context.gqlcore_schema,
self._context.query,
variable_values={
name[1:]: val for name, val in self._context.variables.items()
Expand Down Expand Up @@ -167,8 +164,7 @@ def _visit_query(self, node):
self.visit(node.variables)

# base Query needs to be configured specially
base = self._context.gql_schema.get(
'Query', modules=self._context.modules)
base = self._context.gql_schema.get('Query')

# special treatment of the selection_set, different from inner
# recursion
Expand Down Expand Up @@ -246,12 +242,7 @@ def _is_duplicate_field(self, node):
name = node.alias or node.name
dup = self._context.fields[-1].get(name)
if dup:
if not ast.nodes_equal(dup, node):
raise GraphQLValidationError(
f"field {name!r} has ambiguous definition",
context=node.context)
else:
return True
return True
else:
self._context.fields[-1][name] = node

Expand Down Expand Up @@ -578,23 +569,19 @@ def combine_field_results(self, results, *, flatten=True):
return results


def translate(schema, graphql, *, variables=None, operation_name=None,
modules=None):
def translate(schema, graphql, *, variables=None, operation_name=None):
if variables is None:
variables = {}

if modules is None:
modules = set(modules) | {'default'}

# HACK
query = re.sub(r'@edgedb\(.*?\)', '', graphql)
schema2 = gt.GQLCoreSchema(schema, *modules)._gql_schema
schema2 = gt.GQLCoreSchema(schema)

parser = gqlparser.GraphQLParser()
gqltree = parser.parse(graphql)
context = GraphQLTranslatorContext(
schema=schema, gqlcore=schema2, query=query,
variables=variables, operation_name=operation_name, modules=modules)
variables=variables, operation_name=operation_name)
edge_forest_map = GraphQLTranslator(context=context).visit(gqltree)
code = []
for name, (tree, critvars) in sorted(edge_forest_map.items()):
Expand Down
54 changes: 26 additions & 28 deletions edb/lang/graphql/types.py
Expand Up @@ -33,7 +33,6 @@
GraphQLBoolean,
GraphQLID,
)
import itertools

from edb.lang.edgeql import ast as qlast
from edb.lang.edgeql import codegen
Expand Down Expand Up @@ -65,11 +64,17 @@


class GQLCoreSchema:
def __init__(self, edb_schema, *modules):
'''Create a graphql schema based on specific modules from edgedb.'''
def __init__(self, edb_schema):
'''Create a graphql schema based on edgedb schema.'''

self.edb_schema = edb_schema
self.modules = modules
# extract and sort modules to have a consistent type ordering
self.modules = {
m.name for m in
self.edb_schema.get_modules()
} - {'schema', 'graphql'}
self.modules = list(self.modules)
self.modules.sort()

self._gql_interfaces = {}
self._gql_objtypes = {}
Expand Down Expand Up @@ -131,9 +136,8 @@ def get_fields(self, typename):
fields = OrderedDict()

if typename == 'Query':
for name, gqltype in sorted(itertools.chain(
self._gql_interfaces.items(), self._gql_objtypes.items()),
key=lambda x: x[1].name):
for name, gqltype in sorted(self._gql_interfaces.items(),
key=lambda x: x[1].name):
if name == typename:
continue
fields[name.split('::', 1)[1]] = GraphQLField(
Expand Down Expand Up @@ -176,20 +180,22 @@ def get_args(self, typename):
return args

def _define_types(self):
abstract_types = []
interface_types = []
obj_types = []

for modname in self.modules:
# get all descendants of this abstract type
module = self.edb_schema.get_module(modname)

abstract_types += [t for t in module.get_objects()
if isinstance(t, ObjectType) and t.is_abstract]
obj_types += [t for t in module.get_objects()
if isinstance(t, ObjectType) and not t.is_abstract]
# every ObjectType is reflected as an interface
interface_types += [t for t in module.get_objects()
if isinstance(t, ObjectType)]

# concrete types are also reflected as Type (with a 'Type' postfix)
obj_types += [t for t in interface_types if not t.is_abstract]

# interfaces
for t in abstract_types:
for t in interface_types:
gqltype = GraphQLInterfaceType(
name=self.get_short_name(t.name),
fields=partial(self.get_fields, t.name),
Expand All @@ -206,12 +212,12 @@ def _define_types(self):
continue

for st in t.get_mro():
if (isinstance(st, ObjectType) and st.is_abstract and
if (isinstance(st, ObjectType) and
st.name in self._gql_interfaces):
interfaces.append(self._gql_interfaces[st.name])

gqltype = GraphQLObjectType(
name=self.get_short_name(t.name),
name=self.get_short_name(t.name) + 'Type',
fields=partial(self.get_fields, t.name),
interfaces=interfaces,
)
Expand Down Expand Up @@ -255,10 +261,10 @@ def __contains__(self, item):
class Schema:
'''This is the schema from GQL perspective.'''

def __init__(self, schema, modules):
self._schema = schema
def __init__(self, gqlcore):
self._schema = gqlcore.edb_schema
self._type_map = {}
self.modules = modules
self.modules = gqlcore.modules

@property
def edb_schema(self):
Expand Down Expand Up @@ -505,11 +511,7 @@ class GQLQuery(GQLBaseType):
shadow_fields = {'__typename'}

def __init__(self, schema, **kwargs):
self.modules = kwargs['modules']
self.modules.sort()
# we give unusual full name, so that it doesn't clash with a
# potential ObjectType `Query` in one of the modules
kwargs['name'] = f'{self.modules}--Query'
self.modules = schema.modules
super().__init__(schema, **kwargs)

@property
Expand Down Expand Up @@ -554,11 +556,7 @@ class GQLMutation(GQLBaseType):
edb_type = 'graphql::Mutation'

def __init__(self, schema, **kwargs):
self.modules = kwargs['modules']
self.modules.sort()
# we give unusual full name, so that it doesn't clash with a
# potential ObjectType `Mutation` in one of the modules
kwargs['name'] = f'{self.modules}--Mutation'
self.modules = schema.modules
super().__init__(schema, **kwargs)

@property
Expand Down
7 changes: 1 addition & 6 deletions edb/server/protocol.py
Expand Up @@ -237,14 +237,9 @@ async def _run_script(self, script, *, graphql=False, flags={}):

if graphql:
with timer.timeit('graphql_translation'):
modules = {
m.name for m in
self.backend.schema.get_modules()
} - {'schema', 'graphql'}
script = graphql_compiler.translate(
self.backend.schema, script,
variables={},
modules=modules) + ';'
variables={}) + ';'

with timer.timeit('parse_eql'):
statements = edgeql.parse_block(script)
Expand Down

0 comments on commit 0d8d5a7

Please sign in to comment.