-
Notifications
You must be signed in to change notification settings - Fork 1.2k
make normalized field available to a DataFetcher #1963
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
Conversation
| private final GraphQLObjectType objectType; | ||
| private final GraphQLFieldDefinition fieldDefinition; | ||
| private final List<NormalizedField> children; | ||
| private final boolean isConditional; |
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.
can there be directives on a NormalizedField?
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.
indirectly yes: Every normalized field maps to a merged Field.
See https://github.com/graphql-java/graphql-java/pull/1963/files#diff-844886aefd16bdb049a548e3273cf543R18. And every MergedField has a list of Field, which again might have a directive. This is for query directives.
For schema directives you can look directly at the GraphQLFieldDefinition.
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.
Btw: The reason why all Ast elements are kept in a separate map is that a NormalizedQuery is independent of the Ast of the Query.
There are infinite different Queries which lead to the same NormalizedQuery.
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.
When you say "infinite" it's because of merged fields, right?
This:
fragment mergeIdenticalFields on Dog {
name
name
}
fragment mergeIdenticalAliasesAndFields on Dog {
otherName: name
otherName: name
}
I was looking at what lookahead functionality graphql-js provide and it looks like they expose the AST data structures directly so without building a normalized view of it. GraphQLResolveInfo.fieldNodes
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.
@tinnou it is merged fields and fragments. The example you mentioned are the simple cases.
{pets{name name ... on Dog {name ...F ...F ...F3} ... on Cat {name ...F2}}
fragment F on Pet {name}
fragment F2 on Cat{name ... F}
fragment F3 on Dog {...F name }is the same as
{pets{name}}GraphQL Java also provides the the Ast Fields (FieldNodes in GraphQL.js) in a DataFetcherEnvironment (getFields or getMergedField), but we found this is simply not enough, this is why we are adding NormalizedFields.
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.
To elaborate a bit more: Because of this flexibility in writing your query the Ast is really not a good structure to lookahead (or for example to look back and ask for all directives declared on the parents).
For example an ast field inside a fragment can lead to n normalized fields:
{pets {...F friends @cachable {...F}}}
fragment F on Pet {
name
}name is actually used in two different position of the Ast, so depending on if we are talking about the name of the friends or the name of the Pets we might have a directive @cachable declared on a parent or not. You can imagine how much more complex it can get with multiple fragments etc.
A normalized Query takes away all of this ambiguity and resolves all Fragments/Interfaces/Unions statically.
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.
Ok your first example makes a lot of sense, I was trying to get info through merged field and I modified one of the unit tests to try to get at it and indeed, the field collector is incapable of "knowing" all the fields within the fragments are one and the same.
def "collect fields on inline fragments"() {
def schema = TestUtil.schema("""
type Query {
pets: Pet
}
interface Pet {
name: String
}
type Cat implements Pet {
name: String
}
type Dog implements Pet {
name: String
}
""")
Document document = new Parser().parseDocument("""
{pets{name @skip(if: false) name @include(if: true) ... on Dog {name ...F ...F ...F3} ... on Cat {name ...F2}}}
fragment F on Pet {name}
fragment F2 on Cat{name ...F}
fragment F3 on Dog {...F name }""")
def object = schema.getType("Query") as GraphQLObjectType
NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, null);
FieldCollector fieldCollector = new FieldCollector()
FieldCollectorParameters fieldCollectorParameters = newParameters()
.schema(schema)
.fragments(getOperationResult.fragmentsByName)
.objectType(object)
.build()
Field pets = ((OperationDefinition) document.children[0]).selectionSet.selections[0] as Field
when:
def result = fieldCollector.collectFields(fieldCollectorParameters, mergedField(pets))
then: "only the name fields on Pet interface were captured"
result.subFields.get("name").fields.size() == 2
result.subFields.get("name").fields.forEach {
assert it.directives.size() > 0 && (it.directives.get(0).name == "skip" || it.directives.get(0).name == "include")
}I think now I grasp the full extent of the need to have the normalized query for looking ahead and having your example in the user documentation would be great actually. (as support for explaining the rationale)
For the part of looking back, to ask for the directives declared on the parent field, I want to make sure I understand correctly. In the example case, when you are executing the Pet.name data fetcher, one would like to know whether the parent field has the @cacheable directive?
i.e:
{
pets {
name <--- can ask whether @cacheable is present on pets
friends @cachable {
name <--- can ask whether @cacheable is present on friends
}
}
}
So I guess if we didn't have the normalized query, looking at ExecutionStepInfo of the parent is not sufficient?
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.
@tinnou ExecutionStepInfo is sufficient if you strictly look back and if you only need to know the parent directives from a field inside a DataFetcher.
But keep in mind that it is a result (or execution) based structure: See this comment for a detail example how ESI looks like when you deal with lists. This means that when you traverse the parent hierarchy up in the ESI you need to deal with that.
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.
or to describe it from another angle:
NormalizedQuery is the best you can get without executing a Query: it lays between the Query AST and the actual execution.
…d-to-dfe # Conflicts: # src/main/java/graphql/util/WeakMemoizedSupplier.java
bbakerman
left a comment
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.
We need to add extra helpers somewhere that makes it easier
to find out IF a given field is present say
Like DataEnvSelectionSet does - not 100% sure where that should sit or not
But this PR unlocks heaps of value so lets get it in
|
We've tried this PR and it looks promising. Thanks for jumping on this, we're looking forward to getting this released. |
The code there is great for single level field matching but we have things like in the data selection set today and I would love to get that in here some where |
We have the same thing where we need to collect the field names on a deeper level. |
|
@bbakerman @andimarek it would be great to get this in master as this is the only working solution for unions currently. We've started implementing against this branch and would love helpers like those suggested above but at even the bare functionality would be extremely useful. Perhaps the coming improvements/tweaks can come in separate PRs to unblock this |
|
Superseded by #2079 |
This PR makes the
NormalizedFieldfrom the normalized query available to aDataFetcher.This allows for example to look ahead in a
DataFetcherincluding Fragments on Interfaces on Unions, which is currently not possible.