-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(compiler): validate field merging using the Xing algorithm (#816)
* Add failing test for partially non-overlapping selections 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. * Fix field merging validation when more than 2 fields are in play * clippy * Pull some field merging code into smaller functions * Use high-level representation for field merging validation * Bail out of field merging validation if selection set contains fragment cycles * Make same response shape less mega quadratic recursive * Make same field selection check less mega quadratic recursive * Port subscription validation that uses field selection expansion * Migrate subscription validation errors to use BuildError * clippy * Skip some unnecessary arc clones * deref * The individual checks can now both be implemented linearly * Add many repeated fields validation benchmark * field(a:1, b:2) and field(b:2, a:1) should compare equal * Remove now-invalid assertion This function can take one field from an object type, and another field from an interface type--then their parent types are not equal. * Port field merging tests from graphql-js * Reduce duplicate validation errors from field merging 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. * doc comments * Take iterator as input for expand_selections * Add a cache for merged selection sets * changelog * Rework the ConflictingFieldNames diagnostic * mention fragment definitions validation change * Tweak error reports for conflicting types and arguments * Fix field name conflict tests * Ensure consistent ordering of errors by using indexmap * Push field merging errors directly into DiagnosticList * Remove unnecessary fragment recursion check * Use a map to optimize comparing large argument lists * Benchmark checking a field with very many arguments * update diagnostic message: multiple -> different * remove outdated comment * useless zip * Use Entry::or_default Co-authored-by: Simon Sapin <[email protected]> * make clippy happy with the benchmarking code * Add a recursion limit while merging fields * check -> already_done * Move ArgumentLookup out of the function * Emit an error when the new recursion limit is reached; add test * ref archived article --------- Co-authored-by: Simon Sapin <[email protected]>
- Loading branch information
1 parent
3b180d9
commit af4b599
Showing
21 changed files
with
2,429 additions
and
599 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
use apollo_compiler::ExecutableDocument; | ||
use apollo_compiler::Schema; | ||
use criterion::*; | ||
|
||
fn bench_many_same_field(c: &mut Criterion) { | ||
let schema = | ||
Schema::parse_and_validate("type Query { hello: String! }", "schema.graphql").unwrap(); | ||
let query = format!("{{ {} }}", "hello ".repeat(1_000)); | ||
|
||
c.bench_function("many_same_field", move |b| { | ||
b.iter(|| { | ||
let doc = | ||
ExecutableDocument::parse_and_validate(&schema, &query, "query.graphql").unwrap(); | ||
black_box(doc); | ||
}); | ||
}); | ||
} | ||
|
||
fn bench_many_same_nested_field(c: &mut Criterion) { | ||
let schema = Schema::parse_and_validate( | ||
" | ||
type Nested { hello: String! } | ||
type Query { nested: Nested! } | ||
", | ||
"schema.graphql", | ||
) | ||
.unwrap(); | ||
let query = format!("{{ {} }}", "nested { hello } ".repeat(1_000)); | ||
|
||
c.bench_function("many_same_nested_field", move |b| { | ||
b.iter(|| { | ||
let doc = | ||
ExecutableDocument::parse_and_validate(&schema, &query, "query.graphql").unwrap(); | ||
black_box(doc); | ||
}); | ||
}); | ||
} | ||
|
||
fn bench_many_arguments(c: &mut Criterion) { | ||
let schema = | ||
Schema::parse_and_validate("type Query { hello: String! }", "schema.graphql").unwrap(); | ||
let args = (0..2_000).fold(String::new(), |mut acc, i| { | ||
use std::fmt::Write; | ||
_ = writeln!(&mut acc, "arg{i}: {i}"); | ||
acc | ||
}); | ||
let field = format!("hello({args})"); | ||
let query = format!("{{ {field} {field} }}"); | ||
|
||
c.bench_function("many_arguments", move |b| { | ||
b.iter(|| { | ||
// Will return errors but that's cool | ||
let doc = ExecutableDocument::parse_and_validate(&schema, &query, "query.graphql"); | ||
_ = black_box(doc); | ||
}); | ||
}); | ||
} | ||
|
||
fn bench_many_types(c: &mut Criterion) { | ||
let schema = Schema::parse_and_validate( | ||
" | ||
interface Abstract { | ||
field: Abstract | ||
leaf: Int | ||
} | ||
interface Abstract1 { | ||
field: Abstract | ||
leaf: Int | ||
} | ||
interface Abstract2 { | ||
field: Abstract | ||
leaf: Int | ||
} | ||
type Concrete1 implements Abstract & Abstract1 { | ||
field: Abstract | ||
leaf: Int | ||
} | ||
type Concrete2 implements Abstract & Abstract2 { | ||
field: Abstract | ||
leaf: Int | ||
} | ||
type Query { | ||
field: Abstract | ||
} | ||
", | ||
"schema.graphql", | ||
) | ||
.unwrap(); | ||
let query = format!( | ||
" | ||
fragment multiply on Abstract {{ | ||
field {{ | ||
... on Abstract1 {{ field {{ leaf }} }} | ||
... on Abstract2 {{ field {{ leaf }} }} | ||
... on Concrete1 {{ field {{ leaf }} }} | ||
... on Concrete2 {{ field {{ leaf }} }} | ||
}} | ||
}} | ||
query DeepAbstractConcrete {{ | ||
{open}{close} | ||
}} | ||
", | ||
open = "field { ...multiply ".repeat(100), | ||
close = "}".repeat(100) | ||
); | ||
|
||
c.bench_function("many_types", move |b| { | ||
b.iter(|| { | ||
let doc = | ||
ExecutableDocument::parse_and_validate(&schema, &query, "query.graphql").unwrap(); | ||
black_box(doc); | ||
}); | ||
}); | ||
} | ||
|
||
criterion_group!( | ||
fields, | ||
bench_many_same_field, | ||
bench_many_same_nested_field, | ||
bench_many_arguments, | ||
bench_many_types, | ||
); | ||
criterion_main!(fields); |
9 changes: 2 additions & 7 deletions
9
crates/apollo-compiler/benches/testdata/supergraph_query.graphql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,5 @@ | ||
query Query { | ||
allProducts { | ||
id | ||
topProducts { | ||
sku | ||
createdBy { | ||
totalProductsCreated | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.