Skip to content
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
1 change: 1 addition & 0 deletions src/main/java/graphql/execution/Execution.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder;
import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo;
import static graphql.execution.ExecutionStrategyParameters.newParameters;
import static graphql.execution.nextgen.Common.getOperationRootType;
import static graphql.language.OperationDefinition.Operation.MUTATION;
import static graphql.language.OperationDefinition.Operation.QUERY;
import static graphql.language.OperationDefinition.Operation.SUBSCRIPTION;
Expand Down
116 changes: 106 additions & 10 deletions src/main/java/graphql/normalized/ExecutableNormalizedField.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import graphql.Assert;
import graphql.Internal;
import graphql.Mutable;
import graphql.collect.ImmutableKit;
import graphql.introspection.Introspection;
import graphql.language.Argument;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLUnionType;
import graphql.util.FpKit;
import org.dataloader.impl.Assertions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -61,20 +67,115 @@ private ExecutableNormalizedField(Builder builder) {
this.parent = builder.parent;
}

@Deprecated // doesn't really work
Copy link
Contributor Author

Choose a reason for hiding this comment

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

See the doco below for why this is wrong.

Copy link
Member

Choose a reason for hiding this comment

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

I would suggest removing it then ... this is not a public api and we don't need to deprecate thing we don't want

public boolean isConditional(GraphQLSchema schema) {
if (parent == null) {
return false;
}
return objectTypeNames.size() > 1 || unwrapAll(parent.getType(schema)) != getOneObjectType(schema);
}

/**
* Determines whether this NF needs a fragment to select the field. However, it considers the parent
* output type when determining whether it needs a fragment.
* <p>
* Consider the following schema
*
* <pre>
* interface Animal {
* name: String
* parent: Animal
* }
* type Cat implements Animal {
* name: String
* parent: Cat
* }
* type Dog implements Animal {
* name: String
* parent: Dog
* isGoodBoy: Boolean
* }
* type Query {
* animal: Animal
* }
* </pre>
* <p>
* and the following query
*
* <pre>
* {
* animal {
* parent {
* name
* }
* }
* }
* </pre>
* <p>
* Then we would get the following normalized operation tree
*
* <pre>
* -Query.animal: Animal
* --[Cat, Dog].parent: Cat, Dog
* ---[Cat, Dog].name: String
* </pre>
* <p>
* If we simply checked the {@link #parent}'s {@link #getFieldDefinitions(GraphQLSchema)} that would
* point us to {@code Cat.parent} and {@code Dog.parent} whose output types would incorrectly answer
* our question whether this is conditional?
* <p>
* We MUST consider that the output type of the {@code parent} field is {@code Animal} and
* NOT {@code Cat} or {@code Dog} as their respective impls would say.
*/
public boolean isConditional(GraphQLSchema schema, @Nullable String parentOutputTypeName) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should allow parentOutputTypeName to be null. This makes for an akward api. Or is there a strong reason for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nah, we could make it non-nullable. I'll make that change.

if (parent == null || parentOutputTypeName == null) {
return false;
}

GraphQLType parentOutputType = schema.getType(parentOutputTypeName);

if (parentOutputType instanceof GraphQLInterfaceType) {
GraphQLInterfaceType parentOutputTypeAsInterface = (GraphQLInterfaceType) parentOutputType;
List<GraphQLObjectType> interfaceImpls = schema.getImplementations(parentOutputTypeAsInterface);

// __typename
if (this.fieldName.equals(Introspection.TypeNameMetaFieldDef.getName()) && interfaceImpls.size() == objectTypeNames.size()) {
return false; // Not conditional
}

// If field does not exist on the output type, it is conditional
if (parentOutputTypeAsInterface.getField(fieldName) == null) {
return true; // Conditional
}

return interfaceImpls.size() != objectTypeNames.size();
} else if (parentOutputType instanceof GraphQLUnionType) {
GraphQLUnionType parentOutputTypeAsUnion = (GraphQLUnionType) parentOutputType;

// __typename is the only field in a union type that CAN be NOT conditional
if (this.fieldName.equals(Introspection.TypeNameMetaFieldDef.getName()) && objectTypeNames.size() == parentOutputTypeAsUnion.getTypes().size()) {
return false; // Not conditional
} else {
return true; // Conditional
}
}

if (objectTypeNames.size() > 1) {
return true; // Conditional
}

return parentOutputType != getOneObjectType(schema);
}

@Deprecated // using this is ALMOST always wrong
Copy link
Member

Choose a reason for hiding this comment

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

lets remove this instead of saying it is wrong to use it.

public GraphQLOutputType getType(GraphQLSchema schema) {
return getOneFieldDefinition(schema).getType();
}

@Deprecated // using this is ALMOST always wrong
Copy link
Member

Choose a reason for hiding this comment

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

same here

public GraphQLFieldDefinition getOneFieldDefinition(GraphQLSchema schema) {
GraphQLFieldDefinition fieldDefinition;
GraphQLFieldDefinition introspectionField = resolveIntrospectionField(fieldName, schema);
GraphQLFieldDefinition introspectionField = resolveIntrospectionField(schema, objectTypeNames, fieldName);
if (introspectionField != null) {
return introspectionField;
}
Expand All @@ -84,7 +185,7 @@ public GraphQLFieldDefinition getOneFieldDefinition(GraphQLSchema schema) {
}

public List<GraphQLFieldDefinition> getFieldDefinitions(GraphQLSchema schema) {
GraphQLFieldDefinition fieldDefinition = resolveIntrospectionField(fieldName, schema);
GraphQLFieldDefinition fieldDefinition = resolveIntrospectionField(schema, objectTypeNames, fieldName);
if (fieldDefinition != null) {
return ImmutableList.of(fieldDefinition);
}
Expand All @@ -96,10 +197,10 @@ public List<GraphQLFieldDefinition> getFieldDefinitions(GraphQLSchema schema) {
return builder.build();
}

private static GraphQLFieldDefinition resolveIntrospectionField(String fieldName, GraphQLSchema schema) {
private static GraphQLFieldDefinition resolveIntrospectionField(GraphQLSchema schema, Set<String> objectTypeNames, String fieldName) {
if (fieldName.equals(schema.getIntrospectionTypenameFieldDefinition().getName())) {
return schema.getIntrospectionTypenameFieldDefinition();
} else {
} else if (objectTypeNames.size() == 1 && objectTypeNames.iterator().next().equals(schema.getQueryType().getName())) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Introspection fields only work directly under Query

if (fieldName.equals(schema.getIntrospectionSchemaFieldDefinition().getName())) {
return schema.getIntrospectionSchemaFieldDefinition();
} else if (fieldName.equals(schema.getIntrospectionTypeFieldDefinition().getName())) {
Expand Down Expand Up @@ -199,13 +300,12 @@ public String getSingleObjectTypeName() {
return objectTypeNames.iterator().next();
}

@Deprecated // using this is ALMOST always wrong
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deprecating these, we should never use them. As we discussed on Slack last week too.

Copy link
Member

Choose a reason for hiding this comment

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

same as the others: lets remove it

public GraphQLObjectType getOneObjectType(GraphQLSchema schema) {
return (GraphQLObjectType) schema.getType(objectTypeNames.iterator().next());
}


public String printDetails() {

StringBuilder result = new StringBuilder();
if (getAlias() != null) {
result.append(getAlias()).append(": ");
Expand Down Expand Up @@ -303,7 +403,6 @@ public static class Builder {
private ImmutableList<Argument> astArguments = ImmutableKit.emptyList();

private Builder() {

}

private Builder(ExecutableNormalizedField existing) {
Expand Down Expand Up @@ -374,8 +473,5 @@ public Builder parent(ExecutableNormalizedField parent) {
public ExecutableNormalizedField build() {
return new ExecutableNormalizedField(this);
}


}

}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ private void buildFieldWithChildren(ExecutableNormalizedField field,
CollectNFResult nextLevel = collectFromMergedField(fieldCollectorNormalizedQueryParams, field, mergedField, curLevel + 1);

for (ExecutableNormalizedField child : nextLevel.children) {

field.addChild(child);
ImmutableList<FieldAndAstParent> mergedFieldForChild = nextLevel.normalizedFieldToAstFields.get(child);
normalizedFieldToMergedField.put(child, newMergedField(map(mergedFieldForChild, fieldAndAstParent -> fieldAndAstParent.field)).build());
Expand All @@ -172,8 +171,6 @@ private void buildFieldWithChildren(ExecutableNormalizedField field,
normalizedFieldToMergedField,
coordinatesToNormalizedFields,
curLevel + 1);


}
}

Expand Down Expand Up @@ -218,14 +215,12 @@ public CollectNFResult collectFromMergedField(FieldCollectorNormalizedQueryParam
ExecutableNormalizedField executableNormalizedField,
ImmutableList<FieldAndAstParent> mergedField,
int level) {
GraphQLUnmodifiedType fieldType = unwrapAll(executableNormalizedField.getType(parameters.getGraphQLSchema()));
// if not composite we don't have any selectionSet because it is a Scalar or enum
if (!(fieldType instanceof GraphQLCompositeType)) {
List<GraphQLFieldDefinition> fieldDefs = executableNormalizedField.getFieldDefinitions(parameters.getGraphQLSchema());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed a bug here where we weren't considering ALL the field definitions. There's a test case for this.

https://github.com/graphql-java/graphql-java/blob/6e2218b7fe66d9b1003e0a863b180b2ebc622786/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy#L747-L791

Without this fix, the test would've returned

-Query.animal: Animal
--[Cat, Dog].parent: Cat, Dog
---[Cat].name: String

Instead of

-Query.animal: Animal
--[Cat, Dog].parent: Cat, Dog
---[Cat, Dog].name: String 

Set<GraphQLObjectType> possibleObjects = resolvePossibleObjects(fieldDefs, parameters.getGraphQLSchema());
if (possibleObjects.isEmpty()) {
return new CollectNFResult(Collections.emptyList(), ImmutableListMultimap.of());
}

Set<GraphQLObjectType> possibleObjects = resolvePossibleObjects((GraphQLCompositeType) fieldType, parameters.getGraphQLSchema());

List<CollectedField> collectedFields = new ArrayList<>();
for (FieldAndAstParent fieldAndAstParent : mergedField) {
if (fieldAndAstParent.field.getSelectionSet() == null) {
Expand Down Expand Up @@ -255,8 +250,6 @@ public CollectNFResult collectFromMergedField(FieldCollectorNormalizedQueryParam
public CollectNFResult collectFromOperation(FieldCollectorNormalizedQueryParams parameters,
OperationDefinition operationDefinition,
GraphQLObjectType rootType) {


Set<GraphQLObjectType> possibleObjects = new LinkedHashSet<>();
possibleObjects.add(rootType);
List<CollectedField> collectedFields = new ArrayList<>();
Expand Down Expand Up @@ -363,7 +356,6 @@ private void collectFromSelectionSet(FieldCollectorNormalizedQueryParams paramet
GraphQLCompositeType astTypeCondition,
Set<GraphQLObjectType> possibleObjects
) {

for (Selection<?> selection : selectionSet.getSelections()) {
if (selection instanceof Field) {
collectField(parameters, result, (Field) selection, possibleObjects, astTypeCondition);
Expand Down Expand Up @@ -445,36 +437,46 @@ private void collectField(FieldCollectorNormalizedQueryParams parameters,
return;
}
// this means there is actually no possible type for this field and we are done
if (possibleObjectTypes.size() == 0) {
if (possibleObjectTypes.isEmpty()) {
return;
}
result.add(new CollectedField(field, possibleObjectTypes, astTypeCondition));
}


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

ImmutableSet<GraphQLObjectType> resolvedTypeCondition = resolvePossibleObjects(typeCondition, graphQLSchema);
if (currentOnes.size() == 0) {
if (currentOnes.isEmpty()) {
return resolvedTypeCondition;
}
return Sets.intersection(currentOnes, resolvedTypeCondition);
}

private ImmutableSet<GraphQLObjectType> resolvePossibleObjects(List<GraphQLFieldDefinition> defs, GraphQLSchema graphQLSchema) {
ImmutableSet.Builder<GraphQLObjectType> builder = ImmutableSet.builder();

for (GraphQLFieldDefinition def : defs) {
GraphQLUnmodifiedType outputType = unwrapAll(def.getType());
if (outputType instanceof GraphQLCompositeType) {
builder.addAll(resolvePossibleObjects((GraphQLCompositeType) outputType, graphQLSchema));
}
}

return builder.build();
}

private ImmutableSet<GraphQLObjectType> resolvePossibleObjects(GraphQLCompositeType type, GraphQLSchema graphQLSchema) {
if (type instanceof GraphQLObjectType) {
return ImmutableSet.of((GraphQLObjectType) type);
} else if (type instanceof GraphQLInterfaceType) {
return ImmutableSet.copyOf(graphQLSchema.getImplementations((GraphQLInterfaceType) type));
} else if (type instanceof GraphQLUnionType) {
List types = ((GraphQLUnionType) type).getTypes();
return ImmutableSet.copyOf((types));
return ImmutableSet.copyOf(types);
} else {
return assertShouldNeverHappen();
}

}

}
Loading