def _get_minimal_query_ast_from_macro_ast(macro_ast): """Get a query that should successfully compile to IR if the macro is valid.""" ast_without_macro_directives = remove_directives_from_ast(macro_ast, { directive.name for directive in DIRECTIVES_REQUIRED_IN_MACRO_EDGE_DEFINITION }) # We will add this output directive to make the ast a valid query output_directive = Directive(Name('output'), arguments=[ Argument(Name('out_name'), StringValue('dummy_output_name')) ]) # Shallow copy everything on the path to the first level selection list query_ast = copy(ast_without_macro_directives) root_level_selection = copy(get_only_selection_from_ast(query_ast, GraphQLInvalidMacroError)) first_level_selections = copy(root_level_selection.selection_set.selections) # Add an output to a new or existing __typename field existing_typename_field = None for idx, selection in enumerate(first_level_selections): if isinstance(selection, Field): if selection.name.value == '__typename': # We have a copy of the list, but the elements are references to objects # in macro_ast that we don't want to mutate. So the following copy is necessary. existing_typename_field = copy(selection) existing_typename_field.directives = copy(existing_typename_field.directives) existing_typename_field.directives.append(output_directive) first_level_selections[idx] = existing_typename_field if existing_typename_field is None: first_level_selections.insert(0, Field(Name('__typename'), directives=[output_directive])) # Propagate the changes back to the result_ast root_level_selection.selection_set = SelectionSet(first_level_selections) query_ast.selection_set = SelectionSet([root_level_selection]) return Document([query_ast])
def separate_operations(document_ast): """Separate operations in a given AST document. This function accepts a single AST document which may contain many operations and fragments and returns a collection of AST documents each of which contains a single operation as well the fragment definitions it refers to. """ # Populate metadata and build a dependency graph. visitor = SeparateOperations() visit(document_ast, visitor) operations = visitor.operations fragments = visitor.fragments positions = visitor.positions dep_graph = visitor.dep_graph # For each operation, produce a new synthesized AST which includes only what is # necessary for completing that operation. separated_document_asts = {} for operation in operations: operation_name = op_name(operation) dependencies = set() collect_transitive_dependencies(dependencies, dep_graph, operation_name) # The list of definition nodes to be included for this operation, sorted to # retain the same order as the original document. definitions = [operation] for name in dependencies: definitions.append(fragments[name]) definitions.sort(key=lambda n: positions.get(n, 0)) separated_document_asts[operation_name] = Document( definitions=definitions) return separated_document_asts
def _make_query_plan_recursive(sub_query_node, sub_query_plan, output_join_descriptors): """Recursively copy the structure of sub_query_node onto sub_query_plan. For each child connection contained in sub_query_node, create a new SubQueryPlan for the corresponding child SubQueryNode, add appropriate @filter directive to the child AST, and attach the new SubQueryPlan to the list of children of the input sub-query plan. Args: sub_query_node: SubQueryNode, whose descendents are copied over onto sub_query_plan. It is not modified by this function sub_query_plan: SubQueryPlan, whose list of child query plans and query AST are modified output_join_descriptors: List[OutputJoinDescriptor], describing which outputs should be joined and how """ # Iterate through child connections of query node for child_query_connection in sub_query_node.child_query_connections: child_sub_query_node = child_query_connection.sink_query_node parent_out_name = child_query_connection.source_field_out_name child_out_name = child_query_connection.sink_field_out_name child_query_type = get_only_query_definition( child_sub_query_node.query_ast, GraphQLValidationError) child_query_type_with_filter = _add_filter_at_field_with_output( child_query_type, child_out_name, parent_out_name # @filter's local variable is named the same as the out_name of the parent's @output ) if child_query_type is child_query_type_with_filter: raise AssertionError( u'An @output directive with out_name "{}" is unexpectedly not found in the ' u'AST "{}".'.format(child_out_name, child_query_type)) else: new_child_query_ast = Document( definitions=[child_query_type_with_filter]) # Create new SubQueryPlan for child child_sub_query_plan = SubQueryPlan( query_ast=new_child_query_ast, schema_id=child_sub_query_node.schema_id, parent_query_plan=sub_query_plan, child_query_plans=[], ) # Add new SubQueryPlan to parent's child list sub_query_plan.child_query_plans.append(child_sub_query_plan) # Add information about this edge new_output_join_descriptor = OutputJoinDescriptor( output_names=(parent_out_name, child_out_name), ) output_join_descriptors.append(new_output_join_descriptor) # Recursively repeat on child SubQueryPlans _make_query_plan_recursive(child_sub_query_node, child_sub_query_plan, output_join_descriptors)
def _get_query_document(root_vertex_field_name, root_selections): """Return a Document representing a query with the specified name and selections.""" return Document(definitions=[ OperationDefinition(operation='query', selection_set=SelectionSet(selections=[ Field( name=Name(value=root_vertex_field_name), selection_set=SelectionSet( selections=root_selections, ), directives=[], ) ])) ])
def leave(self, node, *args): if isinstance(node, Document): self.did_leave = True return Document(loc=node.loc, definitions=definitions)
def enter(self, node, *args): if isinstance(node, Document): self.did_enter = True return Document(loc=node.loc, definitions=[])