-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Refactor GraphQL execution #3005
Conversation
@lutter Hi, I started a Pull Request for graphql_parser that introduces Document Transformer graphql-rust/graphql-parser#56 Not sure it's useful but with such trait, we could have one API for transformations and split the transformation logic into multiple pieces. What do you think? |
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.
Small comments from reading ast.rs
.
graphql/src/execution/ast.rs
Outdated
pub struct SelectionSet { | ||
// Map object types to the list of fields that should be selected for | ||
// them | ||
items: Vec<(String, Vec<Field>)>, |
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.
Is items.len() > 1
only when the selection is over a union or interface? If so it would be clarifying to note that in the 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.
It would be nice to use a type
for strings that represent object names (or type names in general, whatever makes more sense).
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've wanted to use something like &s::ObjectType
here, but that's not possible for various reasons. I've now resolved to do the next best thing, which is keeping a copy of all object types in ApiSchema
, and wrapping them in an Arc
. With that, a SelectionSet
now has basically items: Vec<(Arc<s::ObjectType>, Vec<Field>)>
(in reality a little more complicated to work around other shortcomings of s::ObjectType
)
I am curious what you think of that approach.
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.
Looks good! I see that saved some calls to get_object_type_definition
.
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.
Added a comment to clarify when items.len() > 1
Our arbitration charter allows for flexibility around bugfixes, so we are good to go to push this to The Network @lutter. |
I saw that PR and was sad that it was just a PR (I even went as far as trying to implement a visitor, but then decided it's too much work for what we need) Now that I've been through this refactor, I actually think it would be better if
The biggest question around doing that to me is how values should be handled, since they are user-specific, and the Besides these, one big feature request I'd have for I have no idea what the upstream devs would think about these changes, but it would make our lives a lot easier. |
Addressed all the comments so far |
Rebased to latest master and a dded a few commits to replicate the behavior of the current GraphQL execution for some queries that are really invalid |
3727335
to
4c4a261
Compare
This PR is now ready for review (and hopefully merging/rollout soon) I've tested this branch against all the queries we get in the hosted service, and the only difference in responses comes from treating the (invalid) construct of not having a selection on a field with an object type slightly differently: the current implementation includes an empty object (or list of empty objects, depending on the field type) in the response, with this PR such fields do not appear in the response. In other words, for a type like type Person @entity {
id ID!
spouse Person!
friends [Person!]!
} and the query { persons { id spouse friends } } with current |
Query preprocessing eliminated all variable references
This change should not change any behavior, and is purely mechanical in replacing one set of structs with another set of identical structs that we control
That makes it possible to use the same schema for data nad for introspection queries
The old name wasn't matching what we use it for any more
No cows were harmed in making this change
That is demanded by the GraphQL spec. We need to have a predictable order to ensure attestations remain stable, though this change breaks attestations since it changes the order in which fields appear in the output from alphabetical (however a `BTreeMap` orders string keys) to the order in which fields appear in a query. It also allows us to replace `BTreeMaps`, which are fairly memory intensive, with cheaper `Vec`. The test changes all reflect the changed output behavior; they only reorder fields in the expected output but do not otherwise alter the tests. Fixes #2943
Rather than use a string name, use the actual object type to identify types. It's not possible to do this with plain references, for example, because we pass a reference to a SelectionSet to graph::spawn_blocking, so we do the next best thing and use an Arc. Unfortunately, because `graphql_parser` doesn't wrap its object types in an Arc, that means we need to keep a copy of all of them in ApiSchema.
This just shuffles some code around, but doesn't change anything else, in preparation for representing the schema in a way that's more useful to us.
Set ENABLE_GRAPHQL_VALIDATIONS to any value in the environment to enable validations, rather than enabling them by default and disabling them on demand
Make sure that we handle queries that have a selection from a scalar field by ignoring the selection or that have no selection for a non-scalar field by ignoring that field. The latter differs from the previous behavior where the result would contain an entry for such a field, but the data for the field would be an empty object or a list of empty objects.
Instead of immediately reporting an error, treat missing variables as nulls and let the rest of the execution logic deal with nulls
Refactor how we execute GraphQL queries by transforming the AST we get from
graphql_parser
into a form that is easier to execute. The preprocessing step that translates fromgraphql_parser
's AST to our AST does the following:This removes a lot of hard-to-follow code in query execution, especially in
prefetch
.Besides these simplifications, this PR also fixes a few bugs, and changes the order in which fields appear in the response to what the GraphQL spec mandates. This is a breaking change for the network, as it changes the hash for semantically equivalent query responses (cc @That3Percent on rolling this out on the network)
The refactored code also makes a few weaknesses of the current query execution visible, in particular, field merges (from users specifying the same field multiple times, or from fragment expansion) continue to use a yolo strategy that is not entirely standard conformant. This needs to be addressed in a separate PR.
Fixes #2961.