Skip to content
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

feat(compiler): validate field merging using the Xing algorithm #816

Merged
merged 46 commits into from
Feb 14, 2024

Conversation

goto-bus-stop
Copy link
Member

@goto-bus-stop goto-bus-stop commented Jan 25, 2024

Current situation
This is an offending query:

{
  root { ... on A { unrelated } }
  root { ... on B { overlapping } }
  root { ... on C { overlapping } }
}

Here, assume B.overlapping selects an Int!, and C.overlapping selects an Int. Clearly root.overlapping can either be Int! or Int, and so this must be an error. But previous versions of apollo-compiler do not report anything.

Cause
The validation is implemented by drilling in from the top down. The first field it encounters is root. root is selected multiple times so we need to see if its subselections can merge. In main, we do this by comparing every subselection to the first subselection (... on A { unrelated }). Both the B and C subselections can be merged with A, as they don't select any conflicting fields.

Fix
Comparing all selections of the same field, to the first occurrence of the same field is not enough, as other occurrences can conflict with each other but not with the first field. This PR uses a different algorithm that efficiently groups and
compares all the relevant combinations of fields. https://tech.new-work.se/graphql-overlapping-fields-can-be-merged-fast-ea6e92e0a01

closes #819

This should warn about the `root.overlapping` selection being both `Int` and `Int!`
at the same time, but it does not because the validation only compares
each selection to the *first* selection. Neither `B` nor `C` conflict with
the selection on `A`, so the current implementation considers this
valid.
@goto-bus-stop goto-bus-stop changed the title Fix field merging validation when multiple sub-selections partially overlap fix(compiler): validate field merging when multiple sub-selections partially overlap Jan 29, 2024
@goto-bus-stop
Copy link
Member Author

I think i now have a faithful implementation of https://tech.new-work.se/graphql-overlapping-fields-can-be-merged-fast-ea6e92e0a01, except the caching. I think that it's valid for the output type check and the same field name/arguments check to be ran linearly, just like we did before this change, because of the grouping work that's done beforehand and because the recursions are now independent. Both checks assert that the elements they compare are the same, and a == b && a == c implies b == c. Previously that was also true, but the grouping worked differently, and it could miss out on combinations because of that.

Without caching, we end up grouping most selection sets in exactly the same way twice. It should be simple to add but i want to add more tests to make sure I did it right.

This function can take one field from an object type, and another field
from an interface type--then their parent types are not equal.
Field merging validation was applied to operations and fragment
definitions. Selection sets of fragment definitions must always be
checked in the context of an operation or other fragment, and in the end
it always leads to an operation. So we strictly only need to validate
operations to find all issues in all reachable fragments.

This can still output duplicate errors if a fragment contains a conflict
internally, and is used in multiple operations.
Comment on lines 593 to 644
Error: cannot select multiple fields into the same alias `x`
╭─[query.graphql:17:3]
17 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
20 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select multiple fields into the same alias `x`
╭─[query.graphql:17:3]
13 │ x: c
│ ──┬─
│ ╰─── `x` is selected from `Type.c` here
17 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select multiple fields into the same alias `x`
╭─[query.graphql:20:3]
17 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
20 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select multiple fields into the same alias `x`
╭─[query.graphql:20:3]
13 │ x: c
│ ──┬─
│ ╰─── `x` is selected from `Type.c` here
20 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Copy link
Member Author

@goto-bus-stop goto-bus-stop Feb 6, 2024

Choose a reason for hiding this comment

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

In graphql-js this test is specifically meant to check that we don't produce too many warnings, and I think this may be one too many (but it may not be). It would be more obvious to see if this is correct as is, if it included a SelectionPath

Copy link
Contributor

Choose a reason for hiding this comment

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

One too many isn’t too bad, if we emit a warning for every pair in a set it could get annoying for larger sets. Could a single diagnostic contain a Vec of colliding selections instead of just two?

@goto-bus-stop goto-bus-stop changed the title fix(compiler): validate field merging using the Xing algorithm feat(compiler): validate field merging using the Xing algorithm Feb 7, 2024
@SimonSapin
Copy link
Contributor

I made sure the XING blog post has a copy at https://web.archive.org/web/20240208084612/https://tech.new-work.se/graphql-overlapping-fields-can-be-merged-fast-ea6e92e0a01?gi=3e85e33327df

crates/apollo-compiler/src/validation/diagnostics.rs Outdated Show resolved Hide resolved
crates/apollo-compiler/src/executable/mod.rs Show resolved Hide resolved
Comment on lines 593 to 644
Error: cannot select multiple fields into the same alias `x`
╭─[query.graphql:17:3]
17 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
20 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select multiple fields into the same alias `x`
╭─[query.graphql:17:3]
13 │ x: c
│ ──┬─
│ ╰─── `x` is selected from `Type.c` here
17 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select multiple fields into the same alias `x`
╭─[query.graphql:20:3]
17 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
20 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select multiple fields into the same alias `x`
╭─[query.graphql:20:3]
13 │ x: c
│ ──┬─
│ ╰─── `x` is selected from `Type.c` here
20 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Copy link
Contributor

Choose a reason for hiding this comment

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

One too many isn’t too bad, if we emit a warning for every pair in a set it could get annoying for larger sets. Could a single diagnostic contain a Vec of colliding selections instead of just two?

crates/apollo-compiler/src/validation/selection.rs Outdated Show resolved Hide resolved
crates/apollo-compiler/src/validation/selection.rs Outdated Show resolved Hide resolved
crates/apollo-compiler/src/validation/selection.rs Outdated Show resolved Hide resolved
crates/apollo-compiler/src/validation/selection.rs Outdated Show resolved Hide resolved
crates/apollo-compiler/src/validation/selection.rs Outdated Show resolved Hide resolved
crates/apollo-compiler/src/validation/selection.rs Outdated Show resolved Hide resolved
/// Returns potentially overlapping groups of fields. Fields overlap if they are selected from
/// the same concrete type or if they are selected from an abstract type (future schema changes
/// can make any abstract type overlap with any other type).
fn group_by_common_parents(&self, schema: &schema::Schema) -> &Vec<Vec<FieldSelection<'doc>>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this return a Vec of (hash?)set instead of a Vec of Vec?

@goto-bus-stop
Copy link
Member Author

goto-bus-stop commented Feb 13, 2024

Should add a test for the recursion limit here tomorrow morning.

@@ -105,6 +117,7 @@ fn long_fragment_chains_do_not_overflow_stack() {
.expect_err("must have recursion errors");

let expected = expect_test::expect![[r#"
Error: too much recursion
Copy link
Member Author

@goto-bus-stop goto-bus-stop Feb 14, 2024

Choose a reason for hiding this comment

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

This test now hits a recursion limit in two places, once in fragment validation and once in field merging validation, and emitting two errors for it is correct enough in my opinion.

Recursion errors don't give you a lot of information but hopefully nobody actually hits them by accident 🤪

@lrlna
Copy link
Member

lrlna commented Feb 14, 2024

Now that the recursion tests are in, shall we merge?

@goto-bus-stop goto-bus-stop enabled auto-merge (squash) February 14, 2024 11:02
@goto-bus-stop goto-bus-stop merged commit af4b599 into main Feb 14, 2024
12 checks passed
@goto-bus-stop goto-bus-stop deleted the fix-nested-fragment-validation branch February 14, 2024 11:06
@goto-bus-stop goto-bus-stop mentioned this pull request Feb 14, 2024
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.

validate field merging when multiple sub-selections partially overlap
3 participants