Skip to content
Closed
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
235 changes: 235 additions & 0 deletions src/main/java/graphql/normalized/FieldCollectorNormalizedQuery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package graphql.normalized;


import graphql.Assert;
import graphql.Internal;
import graphql.execution.ConditionalNodes;
import graphql.execution.MergedField;
import graphql.language.Field;
import graphql.language.FragmentDefinition;
import graphql.language.FragmentSpread;
import graphql.language.InlineFragment;
import graphql.language.OperationDefinition;
import graphql.language.Selection;
import graphql.language.SelectionSet;
import graphql.schema.GraphQLCompositeType;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLFieldsContainer;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLTypeUtil;
import graphql.schema.GraphQLUnionType;
import graphql.schema.GraphQLUnmodifiedType;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static graphql.Assert.assertTrue;
import static graphql.introspection.Introspection.TypeNameMetaFieldDef;


// special version of FieldCollector to track all possible types per field
@Internal
public class FieldCollectorNormalizedQuery {

private final ConditionalNodes conditionalNodes = new ConditionalNodes();

public List<NormalizedQueryField> collectFields(FieldCollectorNormalizedQueryParams parameters, NormalizedQueryField normalizedQueryField) {
GraphQLUnmodifiedType fieldType = GraphQLTypeUtil.unwrapAll(normalizedQueryField.getFieldDefinition().getType());
// if not composite we don't have any selectionSet because it is a Scalar or enum
if (!(fieldType instanceof GraphQLCompositeType)) {
return Collections.emptyList();
}

Map<String, Map<GraphQLObjectType, NormalizedQueryField>> subFields = new LinkedHashMap<>();
List<String> visitedFragments = new ArrayList<>();
Set<GraphQLObjectType> possibleObjects
= new LinkedHashSet<>(resolvePossibleObjects((GraphQLCompositeType) fieldType, parameters.getGraphQLSchema()));
for (Field field : normalizedQueryField.getMergedField().getFields()) {
if (field.getSelectionSet() == null) {
continue;
}
this.collectFields(parameters,
field.getSelectionSet(),
visitedFragments,
subFields,
possibleObjects,
normalizedQueryField.getFieldDefinition().getType());
}
List<NormalizedQueryField> result = new ArrayList<>();
subFields.values().forEach(setMergedFieldWTCMap -> {
result.addAll(setMergedFieldWTCMap.values());
});
return result;
}

public List<NormalizedQueryField> collectFromOperation(FieldCollectorNormalizedQueryParams parameters,
OperationDefinition operationDefinition,
GraphQLObjectType rootType) {
Map<String, Map<GraphQLObjectType, NormalizedQueryField>> subFields = new LinkedHashMap<>();
List<String> visitedFragments = new ArrayList<>();
Set<GraphQLObjectType> possibleObjects = new LinkedHashSet<>();
possibleObjects.add(rootType);
this.collectFields(parameters, operationDefinition.getSelectionSet(), visitedFragments, subFields, possibleObjects, rootType);
List<NormalizedQueryField> result = new ArrayList<>();
subFields.values().forEach(setMergedFieldWTCMap -> {
result.addAll(setMergedFieldWTCMap.values());
});
return result;
}


private void collectFields(FieldCollectorNormalizedQueryParams parameters,
SelectionSet selectionSet,
List<String> visitedFragments,
Map<String, Map<GraphQLObjectType, NormalizedQueryField>> result,
Set<GraphQLObjectType> possibleObjects,
GraphQLOutputType parentType) {

for (Selection selection : selectionSet.getSelections()) {
if (selection instanceof Field) {
collectField(parameters, result, (Field) selection, possibleObjects, parentType);
} else if (selection instanceof InlineFragment) {
collectInlineFragment(parameters, visitedFragments, result, (InlineFragment) selection, possibleObjects, parentType);
} else if (selection instanceof FragmentSpread) {
collectFragmentSpread(parameters, visitedFragments, result, (FragmentSpread) selection, possibleObjects, parentType);
}
}
}

private void collectFragmentSpread(FieldCollectorNormalizedQueryParams parameters,
List<String> visitedFragments,
Map<String, Map<GraphQLObjectType, NormalizedQueryField>> result,
FragmentSpread fragmentSpread,
Set<GraphQLObjectType> possibleObjects,
GraphQLOutputType parentType) {
if (visitedFragments.contains(fragmentSpread.getName())) {
return;
}
if (!conditionalNodes.shouldInclude(parameters.getVariables(), fragmentSpread.getDirectives())) {
return;
}
visitedFragments.add(fragmentSpread.getName());
FragmentDefinition fragmentDefinition = Assert.assertNotNull(parameters.getFragmentsByName().get(fragmentSpread.getName()));

if (!conditionalNodes.shouldInclude(parameters.getVariables(), fragmentDefinition.getDirectives())) {
return;
}
GraphQLCompositeType newCondition = (GraphQLCompositeType) parameters.getGraphQLSchema().getType(fragmentDefinition.getTypeCondition().getName());
Set<GraphQLObjectType> newConditions = narrowDownPossibleObjects(possibleObjects, newCondition, parameters.getGraphQLSchema());
GraphQLCompositeType newParentType = (GraphQLCompositeType)
Assert.assertNotNull(parameters.getGraphQLSchema().getType(fragmentDefinition.getTypeCondition().getName()));
collectFields(parameters, fragmentDefinition.getSelectionSet(), visitedFragments, result, newConditions, newParentType);
}

private void collectInlineFragment(FieldCollectorNormalizedQueryParams parameters,
List<String> visitedFragments,
Map<String, Map<GraphQLObjectType, NormalizedQueryField>> result,
InlineFragment inlineFragment,
Set<GraphQLObjectType> possibleObjects,
GraphQLOutputType parentType) {
if (!conditionalNodes.shouldInclude(parameters.getVariables(), inlineFragment.getDirectives())) {
return;
}
Set<GraphQLObjectType> newPossibleOjects = possibleObjects;
GraphQLOutputType newParentType = parentType;

if (inlineFragment.getTypeCondition() != null) {
GraphQLCompositeType newCondition = (GraphQLCompositeType) parameters.getGraphQLSchema().getType(inlineFragment.getTypeCondition().getName());
newPossibleOjects = narrowDownPossibleObjects(possibleObjects, newCondition, parameters.getGraphQLSchema());
newParentType = (GraphQLCompositeType)
Assert.assertNotNull(parameters.getGraphQLSchema().getType(inlineFragment.getTypeCondition().getName()));

}
collectFields(parameters, inlineFragment.getSelectionSet(), visitedFragments, result, newPossibleOjects, newParentType);
}

private void collectField(FieldCollectorNormalizedQueryParams parameters,
Map<String, Map<GraphQLObjectType, NormalizedQueryField>> result,
Field field,
Set<GraphQLObjectType> objectTypes,
GraphQLOutputType parentType) {
if (!conditionalNodes.shouldInclude(parameters.getVariables(), field.getDirectives())) {
return;
}
String name = getFieldEntryKey(field);
result.computeIfAbsent(name, ignored -> new LinkedHashMap<>());
Map<GraphQLObjectType, NormalizedQueryField> existingFieldWTC = result.get(name);
for (GraphQLObjectType objectType : objectTypes) {
if (existingFieldWTC.containsKey(objectType)) {
NormalizedQueryField normalizedQueryField = existingFieldWTC.get(objectType);
existingFieldWTC.put(objectType, normalizedQueryField.transform(builder -> {
MergedField mergedField = normalizedQueryField.getMergedField().transform(mergedFieldBuilder -> mergedFieldBuilder.addField(field));
builder.mergedField(mergedField);
}));
} else {
GraphQLFieldsContainer fieldsContainer = (GraphQLFieldsContainer) GraphQLTypeUtil.unwrapAll(parentType);
NormalizedQueryField newFieldWTC = NormalizedQueryField.newQueryExecutionField(field)
.objectType(objectType)
.fieldDefinition(getFieldDefinition(fieldsContainer, field.getName()))
.fieldsContainer(fieldsContainer)
.parentType(parentType)
.build();
existingFieldWTC.put(objectType, newFieldWTC);
}
}
}


private GraphQLFieldDefinition getFieldDefinition(GraphQLCompositeType parentType, String fieldName) {

if (fieldName.equals(TypeNameMetaFieldDef.getName())) {
return TypeNameMetaFieldDef;
}
assertTrue(parentType instanceof GraphQLFieldsContainer, "should not happen : parent type must be an object or interface %s", parentType);
GraphQLFieldsContainer fieldsContainer = (GraphQLFieldsContainer) parentType;
GraphQLFieldDefinition fieldDefinition = fieldsContainer.getFieldDefinition(fieldName);
Assert.assertTrue(fieldDefinition != null, "Unknown field '%s'", fieldName);
return fieldDefinition;
}


private String getFieldEntryKey(Field field) {
if (field.getAlias() != null) {
return field.getAlias();
} else {
return field.getName();
}
}

private Set<GraphQLObjectType> narrowDownPossibleObjects(Set<GraphQLObjectType> currentOnes,
GraphQLCompositeType typeCondition,
GraphQLSchema graphQLSchema) {

List<GraphQLObjectType> resolvedTypeCondition = resolvePossibleObjects(typeCondition, graphQLSchema);
if (currentOnes.size() == 0) {
return new LinkedHashSet<>(resolvedTypeCondition);
}

Set<GraphQLObjectType> result = new LinkedHashSet<>(currentOnes);
result.retainAll(resolvedTypeCondition);
return result;
}

private List<GraphQLObjectType> resolvePossibleObjects(GraphQLCompositeType type, GraphQLSchema graphQLSchema) {
if (type instanceof GraphQLObjectType) {
return Collections.singletonList((GraphQLObjectType) type);
} else if (type instanceof GraphQLInterfaceType) {
return graphQLSchema.getImplementations((GraphQLInterfaceType) type);
} else if (type instanceof GraphQLUnionType) {
List types = ((GraphQLUnionType) type).getTypes();
return new ArrayList<GraphQLObjectType>(types);
} else {
return Assert.assertShouldNeverHappen();
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package graphql.normalized;

import graphql.Assert;
import graphql.Internal;
import graphql.language.FragmentDefinition;
import graphql.schema.GraphQLSchema;

import java.util.LinkedHashMap;
import java.util.Map;

@Internal
public class FieldCollectorNormalizedQueryParams {
private final GraphQLSchema graphQLSchema;
private final Map<String, FragmentDefinition> fragmentsByName;
private final Map<String, Object> variables;

public GraphQLSchema getGraphQLSchema() {
return graphQLSchema;
}

public Map<String, FragmentDefinition> getFragmentsByName() {
return fragmentsByName;
}

public Map<String, Object> getVariables() {
return variables;
}


private FieldCollectorNormalizedQueryParams(GraphQLSchema graphQLSchema,
Map<String, Object> variables,
Map<String, FragmentDefinition> fragmentsByName) {
this.fragmentsByName = fragmentsByName;
this.graphQLSchema = graphQLSchema;
this.variables = variables;
}

public static Builder newParameters() {
return new Builder();
}

public static class Builder {
private GraphQLSchema graphQLSchema;
private final Map<String, FragmentDefinition> fragmentsByName = new LinkedHashMap<>();
private final Map<String, Object> variables = new LinkedHashMap<>();

/**
* @see FieldCollectorNormalizedQueryParams#newParameters()
*/
private Builder() {

}

public Builder schema(GraphQLSchema graphQLSchema) {
this.graphQLSchema = graphQLSchema;
return this;
}

public Builder fragments(Map<String, FragmentDefinition> fragmentsByName) {
this.fragmentsByName.putAll(fragmentsByName);
return this;
}

public Builder variables(Map<String, Object> variables) {
this.variables.putAll(variables);
return this;
}

public FieldCollectorNormalizedQueryParams build() {
Assert.assertNotNull(graphQLSchema, "You must provide a schema");
return new FieldCollectorNormalizedQueryParams(graphQLSchema, variables, fragmentsByName);
}

}
}
31 changes: 31 additions & 0 deletions src/main/java/graphql/normalized/NormalizedQuery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package graphql.normalized;

import graphql.Internal;
import graphql.language.AstPrinter;
import graphql.language.Field;
import graphql.language.SelectionSet;

import java.util.List;

import static graphql.util.FpKit.flatList;
import static graphql.util.FpKit.map;

@Internal
public class NormalizedQuery {

private final List<NormalizedQueryField> rootFields;

public NormalizedQuery(List<NormalizedQueryField> rootFields) {
this.rootFields = rootFields;
}

public List<NormalizedQueryField> getRootFields() {
return rootFields;
}

public String printOriginalQuery() {
List<Field> rootAstFields = flatList(map(rootFields, rootField -> rootField.getMergedField().getFields()));
SelectionSet selectionSet = SelectionSet.newSelectionSet().selections(rootAstFields).build();
return AstPrinter.printAst(selectionSet);
}
}
50 changes: 50 additions & 0 deletions src/main/java/graphql/normalized/NormalizedQueryFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package graphql.normalized;

import graphql.Internal;
import graphql.language.Document;
import graphql.language.NodeUtil;
import graphql.schema.GraphQLSchema;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Internal
public class NormalizedQueryFactory {

/**
* Creates a new Query execution tree for the provided query
*/
public NormalizedQuery createNormalizedQuery(GraphQLSchema graphQLSchema, Document document, String operationName, Map<String, Object> variables) {

NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operationName);

FieldCollectorNormalizedQuery fieldCollector = new FieldCollectorNormalizedQuery();
FieldCollectorNormalizedQueryParams parameters = FieldCollectorNormalizedQueryParams
.newParameters()
.fragments(getOperationResult.fragmentsByName)
.schema(graphQLSchema)
.variables(variables)
.build();

List<NormalizedQueryField> roots = fieldCollector.collectFromOperation(parameters, getOperationResult.operationDefinition, graphQLSchema.getQueryType());

List<NormalizedQueryField> realRoots = new ArrayList<>();
for (NormalizedQueryField root : roots) {
realRoots.add(buildFieldWithChildren(root, fieldCollector, parameters));
}

return new NormalizedQuery(realRoots);
}


private NormalizedQueryField buildFieldWithChildren(NormalizedQueryField field, FieldCollectorNormalizedQuery fieldCollector, FieldCollectorNormalizedQueryParams fieldCollectorNormalizedQueryParams) {
List<NormalizedQueryField> fieldsWithoutChildren = fieldCollector.collectFields(fieldCollectorNormalizedQueryParams, field);
List<NormalizedQueryField> realChildren = new ArrayList<>();
for (NormalizedQueryField fieldWithoutChildren : fieldsWithoutChildren) {
NormalizedQueryField realChild = buildFieldWithChildren(fieldWithoutChildren, fieldCollector, fieldCollectorNormalizedQueryParams);
realChildren.add(realChild);
}
return field.transform(builder -> builder.children(realChildren));
}
}
Loading