-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Improve ExecutableNormalizedField.isConditional for interface fields and fix a bug regarding covariant fields #2638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e0c7a03
1a54d01
d24b0d0
14ca8c5
4579ec7
263bbd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
||
|
|
@@ -61,20 +67,115 @@ private ExecutableNormalizedField(Builder builder) { | |
| this.parent = builder.parent; | ||
| } | ||
|
|
||
| @Deprecated // doesn't really work | ||
| 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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should allow
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| } | ||
|
|
@@ -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); | ||
| } | ||
|
|
@@ -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())) { | ||
|
||
| if (fieldName.equals(schema.getIntrospectionSchemaFieldDefinition().getName())) { | ||
| return schema.getIntrospectionSchemaFieldDefinition(); | ||
| } else if (fieldName.equals(schema.getIntrospectionTypeFieldDefinition().getName())) { | ||
|
|
@@ -199,13 +300,12 @@ public String getSingleObjectTypeName() { | |
| return objectTypeNames.iterator().next(); | ||
| } | ||
|
|
||
| @Deprecated // using this is ALMOST always wrong | ||
|
||
| 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(": "); | ||
|
|
@@ -303,7 +403,6 @@ public static class Builder { | |
| private ImmutableList<Argument> astArguments = ImmutableKit.emptyList(); | ||
|
|
||
| private Builder() { | ||
|
|
||
| } | ||
|
|
||
| private Builder(ExecutableNormalizedField existing) { | ||
|
|
@@ -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 |
|---|---|---|
|
|
@@ -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()); | ||
|
|
@@ -172,8 +171,6 @@ private void buildFieldWithChildren(ExecutableNormalizedField field, | |
| normalizedFieldToMergedField, | ||
| coordinatesToNormalizedFields, | ||
| curLevel + 1); | ||
|
|
||
|
|
||
| } | ||
| } | ||
|
|
||
|
|
@@ -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()); | ||
|
||
| 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) { | ||
|
|
@@ -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<>(); | ||
|
|
@@ -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); | ||
|
|
@@ -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(); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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