Skip to content

Commit

Permalink
Merge branch 'master' into repro_for_type_error_and_none
Browse files Browse the repository at this point in the history
  • Loading branch information
obi1kenobi committed Dec 25, 2018
2 parents 49423bc + d12336d commit 67120c1
Show file tree
Hide file tree
Showing 17 changed files with 429 additions and 78 deletions.
49 changes: 45 additions & 4 deletions graphql_compiler/__init__.py
@@ -1,7 +1,12 @@
# Copyright 2017-present Kensho Technologies, LLC.
"""Commonly-used functions and data types from this package."""
from .compiler import CompilationResult, OutputMetadata # noqa
from .compiler import compile_graphql_to_gremlin, compile_graphql_to_match # noqa
from .compiler import ( # noqa
CompilationResult,
OutputMetadata,
compile_graphql_to_gremlin,
compile_graphql_to_match,
compile_graphql_to_sql,
)
from .query_formatting import insert_arguments_into_query # noqa
from .query_formatting.graphql_formatting import pretty_print_graphql # noqa

Expand All @@ -22,7 +27,7 @@ def graphql_to_match(schema, graphql_query, parameters, type_equivalence_hints=N
Args:
schema: GraphQL schema object describing the schema of the graph to be queried
graphql_string: the GraphQL query to compile to MATCH, as a string
graphql_query: the GraphQL query to compile to MATCH, as a string
parameters: dict, mapping argument name to its value, for every parameter the query expects.
type_equivalence_hints: optional dict of GraphQL interface or type -> GraphQL union.
Used as a workaround for GraphQL's lack of support for
Expand Down Expand Up @@ -53,12 +58,48 @@ def graphql_to_match(schema, graphql_query, parameters, type_equivalence_hints=N
query=insert_arguments_into_query(compilation_result, parameters))


def graphql_to_sql(schema, graphql_query, parameters, compiler_metadata,
type_equivalence_hints=None):
"""Compile the GraphQL input using the schema into a SQL query and associated metadata.
Args:
schema: GraphQL schema object describing the schema of the graph to be queried
graphql_query: the GraphQL query to compile to SQL, as a string
parameters: dict, mapping argument name to its value, for every parameter the query expects.
compiler_metadata: CompilerMetadata object, provides SQLAlchemy specific backend
information
type_equivalence_hints: optional dict of GraphQL interface or type -> GraphQL union.
Used as a workaround for GraphQL's lack of support for
inheritance across "types" (i.e. non-interfaces), as well as a
workaround for Gremlin's total lack of inheritance-awareness.
The key-value pairs in the dict specify that the "key" type
is equivalent to the "value" type, i.e. that the GraphQL type or
interface in the key is the most-derived common supertype
of every GraphQL type in the "value" GraphQL union.
Recursive expansion of type equivalence hints is not performed,
and only type-level correctness of this argument is enforced.
See README.md for more details on everything this parameter does.
*****
Be very careful with this option, as bad input here will
lead to incorrect output queries being generated.
*****
Returns:
a CompilationResult object, containing:
- query: string, the resulting compiled and parameterized query string
- language: string, specifying the language to which the query was compiled
- output_metadata: dict, output name -> OutputMetadata namedtuple object
- input_metadata: dict, name of input variables -> inferred GraphQL type, based on use
"""
raise NotImplementedError(u'Compiling GraphQL to SQL is not yet supported.')


def graphql_to_gremlin(schema, graphql_query, parameters, type_equivalence_hints=None):
"""Compile the GraphQL input using the schema into a Gremlin query and associated metadata.
Args:
schema: GraphQL schema object describing the schema of the graph to be queried
graphql_string: the GraphQL query to compile to Gremlin, as a string
graphql_query: the GraphQL query to compile to Gremlin, as a string
parameters: dict, mapping argument name to its value, for every parameter the query expects.
type_equivalence_hints: optional dict of GraphQL interface or type -> GraphQL union.
Used as a workaround for GraphQL's lack of support for
Expand Down
9 changes: 7 additions & 2 deletions graphql_compiler/compiler/__init__.py
@@ -1,4 +1,9 @@
# Copyright 2017-present Kensho Technologies, LLC.
from .common import CompilationResult, compile_graphql_to_gremlin, compile_graphql_to_match # noqa
from .common import GREMLIN_LANGUAGE, MATCH_LANGUAGE # noqa
from .common import ( # noqa
CompilationResult,
compile_graphql_to_gremlin,
compile_graphql_to_match,
compile_graphql_to_sql,
)
from .common import GREMLIN_LANGUAGE, MATCH_LANGUAGE, SQL_LANGUAGE # noqa
from .compiler_frontend import OutputMetadata # noqa
26 changes: 26 additions & 0 deletions graphql_compiler/compiler/blocks.py
Expand Up @@ -13,6 +13,8 @@
class QueryRoot(BasicBlock):
"""The starting object of the query to be compiled."""

__slots__ = ('start_class',)

def __init__(self, start_class):
"""Construct a QueryRoot object that starts querying at the specified class name.
Expand Down Expand Up @@ -56,6 +58,8 @@ def to_gremlin(self):
class CoerceType(BasicBlock):
"""A special type of filter that discards any data that is not of the specified set of types."""

__slots__ = ('target_class',)

def __init__(self, target_class):
"""Construct a CoerceType object that filters out any data that is not of the given types.
Expand Down Expand Up @@ -91,6 +95,8 @@ def to_gremlin(self):
class ConstructResult(BasicBlock):
"""A transformation of the data into a new form, for output."""

__slots__ = ('fields',)

def __init__(self, fields):
"""Construct a ConstructResult object that maps the given field names to their expressions.
Expand Down Expand Up @@ -157,6 +163,8 @@ def to_gremlin(self):
class Filter(BasicBlock):
"""A filter that ensures data matches a predicate expression, and discards all other data."""

__slots__ = ('predicate',)

def __init__(self, predicate):
"""Create a new Filter with the specified Expression as a predicate."""
super(Filter, self).__init__(predicate)
Expand Down Expand Up @@ -186,6 +194,8 @@ def to_gremlin(self):
class MarkLocation(BasicBlock):
"""A block that assigns a name to a given location in the query."""

__slots__ = ('location',)

def __init__(self, location):
"""Create a new MarkLocation at the specified Location.
Expand Down Expand Up @@ -213,6 +223,8 @@ def to_gremlin(self):
class Traverse(BasicBlock):
"""A block that encodes a traversal across an edge, in either direction."""

__slots__ = ('direction', 'edge_name', 'optional', 'within_optional_scope')

def __init__(self, direction, edge_name, optional=False, within_optional_scope=False):
"""Create a new Traverse block in the given direction and across the given edge.
Expand Down Expand Up @@ -296,6 +308,8 @@ def to_gremlin(self):
class Recurse(BasicBlock):
"""A block for recursive traversal of an edge, collecting all endpoints along the way."""

__slots__ = ('direction', 'edge_name', 'depth', 'within_optional_scope')

def __init__(self, direction, edge_name, depth, within_optional_scope=False):
"""Create a new Recurse block which traverses the given edge up to "depth" times.
Expand Down Expand Up @@ -360,6 +374,8 @@ def to_gremlin(self):
class Backtrack(BasicBlock):
"""A block that specifies a return to a given Location in the query."""

__slots__ = ('location', 'optional')

def __init__(self, location, optional=False):
"""Create a new Backtrack block, returning to the given location in the query.
Expand Down Expand Up @@ -409,6 +425,8 @@ class OutputSource(MarkerBlock):
See the comment on the @output_source directive in schema.py on why this is necessary.
"""

__slots__ = ()

def validate(self):
"""Validate the OutputSource block. An OutputSource block is always valid in isolation."""
pass
Expand All @@ -417,6 +435,8 @@ def validate(self):
class Fold(MarkerBlock):
"""A marker for the start of a @fold context."""

__slots__ = ('fold_scope_location',)

def __init__(self, fold_scope_location):
"""Create a new Fold block rooted at the given location."""
super(Fold, self).__init__(fold_scope_location)
Expand All @@ -433,6 +453,8 @@ def validate(self):
class Unfold(MarkerBlock):
"""A marker for the end of a @fold context."""

__slots__ = ()

def validate(self):
"""Unfold blocks are always valid in isolation."""
pass
Expand All @@ -444,6 +466,8 @@ class EndOptional(MarkerBlock):
Optional scope is entered through an optional Traverse Block.
"""

__slots__ = ()

def validate(self):
"""In isolation, EndOptional blocks are always valid."""
pass
Expand All @@ -452,6 +476,8 @@ def validate(self):
class GlobalOperationsStart(MarkerBlock):
"""Marker block for the end of MATCH traversals, and the beginning of global operations."""

__slots__ = ()

def validate(self):
"""In isolation, GlobalOperationsStart blocks are always valid."""
pass
54 changes: 49 additions & 5 deletions graphql_compiler/compiler/common.py
Expand Up @@ -15,6 +15,7 @@

MATCH_LANGUAGE = 'MATCH'
GREMLIN_LANGUAGE = 'Gremlin'
SQL_LANGUAGE = 'SQL'


def compile_graphql_to_match(schema, graphql_string, type_equivalence_hints=None):
Expand Down Expand Up @@ -47,7 +48,7 @@ def compile_graphql_to_match(schema, graphql_string, type_equivalence_hints=None

return _compile_graphql_generic(
MATCH_LANGUAGE, lowering_func, query_emitter_func,
schema, graphql_string, type_equivalence_hints)
schema, graphql_string, type_equivalence_hints, None)


def compile_graphql_to_gremlin(schema, graphql_string, type_equivalence_hints=None):
Expand Down Expand Up @@ -80,20 +81,63 @@ def compile_graphql_to_gremlin(schema, graphql_string, type_equivalence_hints=No

return _compile_graphql_generic(
GREMLIN_LANGUAGE, lowering_func, query_emitter_func,
schema, graphql_string, type_equivalence_hints)
schema, graphql_string, type_equivalence_hints, None)


def compile_graphql_to_sql(schema, graphql_string, compiler_metadata, type_equivalence_hints=None):
"""Compile the GraphQL input using the schema into a SQL query and associated metadata.
Args:
schema: GraphQL schema object describing the schema of the graph to be queried
graphql_string: the GraphQL query to compile to SQL, as a string
compiler_metadata: SQLAlchemy metadata containing tables for use during compilation.
type_equivalence_hints: optional dict of GraphQL interface or type -> GraphQL union.
Used as a workaround for GraphQL's lack of support for
inheritance across "types" (i.e. non-interfaces), as well as a
workaround for Gremlin's total lack of inheritance-awareness.
The key-value pairs in the dict specify that the "key" type
is equivalent to the "value" type, i.e. that the GraphQL type or
interface in the key is the most-derived common supertype
of every GraphQL type in the "value" GraphQL union.
Recursive expansion of type equivalence hints is not performed,
and only type-level correctness of this argument is enforced.
See README.md for more details on everything this parameter does.
*****
Be very careful with this option, as bad input here will
lead to incorrect output queries being generated.
*****
Returns:
a CompilationResult object
"""
raise NotImplementedError(u'Compiling GraphQL to SQL is not yet supported.')


def _compile_graphql_generic(language, lowering_func, query_emitter_func,
schema, graphql_string, type_equivalence_hints):
"""Compile the GraphQL input, lowering and emitting the query using the given functions."""
schema, graphql_string, type_equivalence_hints, compiler_metadata):
"""Compile the GraphQL input, lowering and emitting the query using the given functions.
Args:
language: string indicating the target language to compile to.
lowering_func: Function to lower the compiler IR into a compatible form for the target
language backend.
query_emitter_func: Function that emits a query in the target language from the lowered IR.
schema: GraphQL schema object describing the schema of the graph to be queried.
graphql_string: the GraphQL query to compile to the target language, as a string.
type_equivalence_hints: optional dict of GraphQL interface or type -> GraphQL union.
compiler_metadata: optional target specific metadata for usage by the query_emitter_func.
Returns:
a CompilationResult object
"""
ir_and_metadata = graphql_to_ir(
schema, graphql_string, type_equivalence_hints=type_equivalence_hints)

lowered_ir_blocks = lowering_func(
ir_and_metadata.ir_blocks, ir_and_metadata.query_metadata_table,
type_equivalence_hints=type_equivalence_hints)

query = query_emitter_func(lowered_ir_blocks)
query = query_emitter_func(lowered_ir_blocks, compiler_metadata)

return CompilationResult(
query=query,
Expand Down
8 changes: 8 additions & 0 deletions graphql_compiler/compiler/compiler_entities.py
Expand Up @@ -11,6 +11,8 @@
class CompilerEntity(object):
"""An abstract compiler entity. Can represent things like basic blocks and expressions."""

__slots__ = ('_print_args', '_print_kwargs')

def __init__(self, *args, **kwargs):
"""Construct a new CompilerEntity."""
self._print_args = args
Expand Down Expand Up @@ -60,6 +62,8 @@ def to_gremlin(self):
class Expression(CompilerEntity):
"""An expression that produces a value in the GraphQL compiler."""

__slots__ = ()

def visit_and_update(self, visitor_fn):
"""Create an updated version (if needed) of the Expression via the visitor pattern.
Expand All @@ -86,6 +90,8 @@ def visit_and_update(self, visitor_fn):
class BasicBlock(CompilerEntity):
"""A basic operation block of the GraphQL compiler."""

__slots__ = ()

def visit_and_update_expressions(self, visitor_fn):
"""Create an updated version (if needed) of the BasicBlock via the visitor pattern.
Expand Down Expand Up @@ -113,6 +119,8 @@ def visit_and_update_expressions(self, visitor_fn):
class MarkerBlock(BasicBlock):
"""A block that is used to mark that a context-affecting operation with no output happened."""

__slots__ = ()

def to_gremlin(self):
"""Return the Gremlin representation of the block, which should almost always be empty.
Expand Down

0 comments on commit 67120c1

Please sign in to comment.