Skip to content

Conversation

@bbakerman
Copy link
Member

This is the same code as #2905 but with an extra test for SchemaTransformer which also fails

@bbakerman
Copy link
Member Author

I debugged this further. The reason this blows up (even for SchemaTransformer changes along with direct ones) is this

The first step of the schema build code calls this

            SchemaUtil.visitPartiallySchema(partiallyBuiltSchema, codeRegistryVisitor, typeCollectingVisitor);
            ImmutableMap<String, GraphQLNamedType> allTypes = typeCollectingVisitor.getResult();

At this point the map is meant to contain a map of name -> ACTUAL types (and not type references)

Later the type reference replacement code finds all type references and expects to find the actual type for them in the map above and its does not and asserts.

However this particular edit means that the schema DOES have a field pointer to a actual type but the GraphQLTypeCollectingVisitor never grabs it because it only visits types via direct declaration and not via field declaration.

This makes sense in SDL generation because its impossible to create a situation where the actual type is not declared but code declaration can make this happen as demonstrated in this PR test case.

@bbakerman
Copy link
Member Author

Ok debugging more - this is more about the Traverser related code

In this "edit case" we have a field b that at some point earlier had a type reference to B in the field def. On the original schema build this gets resolved to an actual type. So now the field reference has originalType=typeRef("B") and also replacedType=actualTypeB

Then the code does and edit and this gets rid of the direct reference to type B (which came via another field chain) but this field b still has originalType=typeRef("B") andreplacedType=actualTypeB.

So Type B is still around in some form

The graphql.schema.impl.GraphQLTypeCollectingVisitor#GraphQLTypeCollectingVisitor setup code does this

        traverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList());

and the callback to the GraphqlFieldDefinition is getChildrenWithTypeReferences which is code as

        return SchemaElementChildrenContainer.newSchemaElementChildrenContainer()
                .child(CHILD_TYPE, originalType)
                .children(CHILD_ARGUMENTS, arguments)
                .children(CHILD_DIRECTIVES, directivesHolder.getDirectives())
                .children(CHILD_APPLIED_DIRECTIVES, directivesHolder.getAppliedDirectives())
                .build();

So while the replace type is in place - its never used.

The question is - should it be?

@bbakerman
Copy link
Member Author

The code rightly wants a DAG - if we change it, it starts to fail with

Internal error: should never happen: This schema is not forming an Directed Acyclic Graph

The challenge here is this. The edit caused the field to be the only reference to the type B BUT the traverser in the schema build uses getChildrenWithTypeReferences and this missed this type.

It's as if the specific use case GraphQLTypeCollectingVisitor needs to grab dangling field references and do a fix up if they are missing - eg type referenced but not in the schema in some other way

}
type = unwrapOne(type);
}
return unwrapAllAs(type);
Copy link
Member Author

Choose a reason for hiding this comment

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

This was probably a mistake to have the well named unwrapAll return GraphQLUnmodifiedType which excludes GraphqlTypeReferences - but API is API so leave it as is

I did change how it works internally here

GraphQLNamedType type = unwrapAllAs(node.getType());
if (! (type instanceof GraphQLTypeReference)) {
fieldActualTypes.put(type.getName(), type);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

It only puts in fields that unwrap to be an actual type

private Map<String, GraphQLNamedType> fixFieldDanglingTypes(Map<String, GraphQLNamedType> visitedTypes) {
for (GraphQLNamedType fieldPointerType : fieldActualTypes.values()) {
String typeName = fieldPointerType.getName();
visitedTypes.putIfAbsent(typeName, fieldPointerType);
Copy link
Member Author

Choose a reason for hiding this comment

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

if we have a type pointed to by a field AND it's not the the list of visited types, we add it since the field is the only pointer to it say

throw new AssertException(format("All types within a GraphQL schema must have unique names. No two provided types may have the same name.\n" +
"No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types).\n" +
"You have redefined the type '%s' from being a '%s' to a '%s'",
type.getName(), existingType.getClass().getSimpleName(), type.getClass().getSimpleName()));
Copy link
Member Author

Choose a reason for hiding this comment

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

made a common method here


then:
transformed.containsType("B")

Copy link
Member Author

Choose a reason for hiding this comment

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

Andres original test that was failing

GraphQLSchema transformed = SchemaTransformer.transformSchema(schema, visitor)

then:
transformed.containsType("B")
Copy link
Member Author

Choose a reason for hiding this comment

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

My SchemaTransformer test that was failing and now passing

then:
schema.getType("A") != null
schema.getType("B") != null
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Direct object test case (no SDL) that was failing and is now passing

then:
typeRef instanceof GraphQLTypeReference
(typeRef as GraphQLTypeReference).name == "A"

Copy link
Member Author

Choose a reason for hiding this comment

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

Extra tests as unwrapAllAs was not previously covered

@bbakerman bbakerman changed the title Xuorig repro PR - Edge case with GraphQLTypeReference and Schema Transforms Xuorig Fix PR - Edge case with GraphQLTypeReference and Schema Transforms Aug 2, 2022

when:
def schema = TestUtil.schema(sdl)
// When field `a` is filtered out
Copy link
Member

Choose a reason for hiding this comment

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

b is filtered out

Copy link
Member Author

Choose a reason for hiding this comment

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

updated

@bbakerman bbakerman added this to the 19.1 milestone Aug 2, 2022
assertTypeUniqueness(node, result);
save(node.getName(), node);
return super.visitGraphQLScalarType(node, context);
return CONTINUE;
Copy link
Member Author

Choose a reason for hiding this comment

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

all of this super code boiled down to CONTINUE!

Copy link

@xuorig xuorig left a comment

Choose a reason for hiding this comment

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

Appreciate the fix! Very similar solution to the workaround I implemented but much much nicer now that it is built in in the type collector. Thanks 🙇

@bbakerman bbakerman merged commit fb507ad into master Aug 11, 2022
estal011 added a commit to 8btc-OnePiece/graphql-java that referenced this pull request Jun 6, 2024
* 19.3: (709 commits)
  use class loader in getResource (graphql-java#3038)
  Stable port of graphql-java#2940 (graphql-java#2947)
  Stable port of Diff counts are the same (graphql-java#2946)
  Stable port of Fix printing directives when they contain something like a formatting specifier (graphql-java#2919) (graphql-java#2920) (graphql-java#2945)
  Stable port of Fix field visibility bug with enum with enum args (graphql-java#2926) (graphql-java#2944)
  Stable fix for graphql-java#2943 (graphql-java#2943)
  Added test fore intersection
  Xuorig Fix PR - Edge case with GraphQLTypeReference and Schema Transforms (graphql-java#2906)
  Fix typo in description of skip directive (graphql-java#2915)
  Add smaller set first optimisation
  Cheaper calculation for narrowing down possible objects in ENO
  Handles isDeprecated not being present in the json
  Defaults Locale when calling validation (graphql-java#2908)
  DF SelectionSet Benchmark (graphql-java#2893)
  Test stability (graphql-java#2903)
  Donna's catch! (graphql-java#2900)
  Deprecate Apollo Cache Control
  READY - Stop DOS attacks by making the lexer stop early on evil input. (graphql-java#2892)
  Bump java-dataloader ahead of release
  State is passed explicitly to instrumentation and parameters are NOT mutated (graphql-java#2769)
  ...

# Conflicts:
#	README.md
#	build.gradle
#	src/main/java/graphql/GraphQL.java
#	src/main/java/graphql/Scalars.java
#	src/main/java/graphql/execution/ValuesResolver.java
#	src/main/java/graphql/relay/SimpleListConnection.java
#	src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants