diff --git a/src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java b/src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java index 683138e387..2a2b237aeb 100644 --- a/src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java +++ b/src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java @@ -203,7 +203,7 @@ public TraversalControl visitArgument(Argument argument, TraverserContext QueryVisitorFieldEnvironment fieldEnv = fieldCtx.getEnvironment(); GraphQLFieldsContainer fieldsContainer = fieldEnv.getFieldsContainer(); - GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDef(schema, fieldsContainer, field.getName()); + GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDefinition(schema, fieldsContainer, field.getName()); GraphQLArgument graphQLArgument = fieldDefinition.getArgument(argument.getName()); String argumentName = graphQLArgument.getName(); diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index 5da3cefbbf..26e126fd79 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -946,7 +946,7 @@ protected GraphQLFieldDefinition getFieldDef(ExecutionContext executionContext, * @return a {@link GraphQLFieldDefinition} */ protected GraphQLFieldDefinition getFieldDef(GraphQLSchema schema, GraphQLObjectType parentType, Field field) { - return Introspection.getFieldDef(schema, parentType, field.getName()); + return Introspection.getFieldDefinition(schema, parentType, field.getName()); } /** diff --git a/src/main/java/graphql/introspection/Introspection.java b/src/main/java/graphql/introspection/Introspection.java index f20f58d238..ced6cbf818 100644 --- a/src/main/java/graphql/introspection/Introspection.java +++ b/src/main/java/graphql/introspection/Introspection.java @@ -784,7 +784,8 @@ public static boolean isIntrospectionTypes(String typeName) { /** * This will look up a field definition by name, and understand that fields like __typename and __schema are special - * and take precedence in field resolution + * and take precedence in field resolution. If the parent type is a union type, then the only field allowed + * is `__typename`. * * @param schema the schema to use * @param parentType the type of the parent object @@ -794,6 +795,43 @@ public static boolean isIntrospectionTypes(String typeName) { */ public static GraphQLFieldDefinition getFieldDef(GraphQLSchema schema, GraphQLCompositeType parentType, String fieldName) { + GraphQLFieldDefinition fieldDefinition = getSystemFieldDef(schema, parentType, fieldName); + if (fieldDefinition != null) { + return fieldDefinition; + } + + assertTrue(parentType instanceof GraphQLFieldsContainer, "should not happen : parent type must be an object or interface %s", parentType); + GraphQLFieldsContainer fieldsContainer = (GraphQLFieldsContainer) parentType; + fieldDefinition = schema.getCodeRegistry().getFieldVisibility().getFieldDefinition(fieldsContainer, fieldName); + assertTrue(fieldDefinition != null, "Unknown field '%s' for type %s", fieldName, fieldsContainer.getName()); + return fieldDefinition; + } + + /** + * This will look up a field definition by name, and understand that fields like __typename and __schema are special + * and take precedence in field resolution + * + * @param schema the schema to use + * @param parentType the type of the parent {@link GraphQLFieldsContainer} + * @param fieldName the field to look up + * + * @return a field definition otherwise throws an assertion exception if it's null + */ + public static GraphQLFieldDefinition getFieldDefinition(GraphQLSchema schema, GraphQLFieldsContainer parentType, String fieldName) { + // this method is optimized to look up the most common case first (type for field) and hence suits the hot path of the execution engine + // and as a small benefit does not allocate any assertions unless it completely failed + GraphQLFieldDefinition fieldDefinition = schema.getCodeRegistry().getFieldVisibility().getFieldDefinition(parentType, fieldName); + if (fieldDefinition == null) { + // we look up system fields second because they are less likely to be the field in question + fieldDefinition = getSystemFieldDef(schema, parentType, fieldName); + if (fieldDefinition == null) { + Assert.assertShouldNeverHappen(String.format("Unknown field '%s' for type %s", fieldName, parentType.getName())); + } + } + return fieldDefinition; + } + + private static GraphQLFieldDefinition getSystemFieldDef(GraphQLSchema schema, GraphQLCompositeType parentType, String fieldName) { if (schema.getQueryType() == parentType) { if (fieldName.equals(schema.getIntrospectionSchemaFieldDefinition().getName())) { return schema.getIntrospectionSchemaFieldDefinition(); @@ -805,11 +843,6 @@ public static GraphQLFieldDefinition getFieldDef(GraphQLSchema schema, GraphQLCo if (fieldName.equals(schema.getIntrospectionTypenameFieldDefinition().getName())) { return schema.getIntrospectionTypenameFieldDefinition(); } - - assertTrue(parentType instanceof GraphQLFieldsContainer, "Should not happen : parent type must be an object or interface %s", parentType); - GraphQLFieldsContainer fieldsContainer = (GraphQLFieldsContainer) parentType; - GraphQLFieldDefinition fieldDefinition = schema.getCodeRegistry().getFieldVisibility().getFieldDefinition(fieldsContainer, fieldName); - assertTrue(fieldDefinition != null, "Unknown field '%s' for type %s", fieldName, fieldsContainer.getName()); - return fieldDefinition; + return null; } -} +} \ No newline at end of file diff --git a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java index 735ab899d9..7ed78470d5 100644 --- a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java +++ b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java @@ -34,6 +34,7 @@ import graphql.schema.FieldCoordinates; import graphql.schema.GraphQLCompositeType; import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLFieldsContainer; import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLNamedOutputType; import graphql.schema.GraphQLObjectType; @@ -644,7 +645,7 @@ private ExecutableNormalizedField createNF(CollectedFieldGroup collectedFieldGro Set objectTypes = collectedFieldGroup.objectTypes; field = collectedFieldGroup.fields.iterator().next().field; String fieldName = field.getName(); - GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDef(graphQLSchema, objectTypes.iterator().next(), fieldName); + GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDefinition(graphQLSchema, objectTypes.iterator().next(), fieldName); Map argumentValues = ValuesResolver.getArgumentValues(fieldDefinition.getArguments(), field.getArguments(), CoercedVariables.of(this.coercedVariableValues.toMap()), this.options.graphQLContext, this.options.locale); Map normalizedArgumentValues = null;