Skip to content

Commit 7fe1465

Browse files
authored
fix: ensure we are removing ALL useless groups (#3163)
When removing "useless" fetch nodes/groups we remove them in-place while still iterating over the same list. This leads to potentially skipping processing of some the children fetch nodes, as when we remove nodes we left shift all remaining children but the iterator keeps the old position unchanged effectively skipping next child. <!-- FED-386 --->
1 parent cc45734 commit 7fe1465

File tree

3 files changed

+137
-1
lines changed

3 files changed

+137
-1
lines changed

.changeset/quiet-rings-fly.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@apollo/query-planner": patch
3+
---
4+
5+
Ensure all useless fetch groups are removed
6+
7+
When removing "useless" fetch nodes/groups we remove them in-place while still iterating over the same list. This leads to potentially skipping processing of some the children fetch nodes, as when we remove nodes we left shift all remaining children but the iterator keeps the old position unchanged effectively skipping next child.

query-planner-js/src/__tests__/buildPlan.test.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10124,3 +10124,128 @@ describe('@fromContext impacts on query planning', () => {
1012410124
]);
1012510125
});
1012610126
});
10127+
10128+
test('ensure we are removing all useless groups', () => {
10129+
const subgraph1 = {
10130+
name: 'Subgraph1',
10131+
typeDefs: gql`
10132+
type Query {
10133+
foo: Foo!
10134+
}
10135+
10136+
type Foo {
10137+
item: I
10138+
}
10139+
10140+
type I @interfaceObject @key(fields: "id") {
10141+
id: ID!
10142+
}
10143+
`,
10144+
};
10145+
10146+
const subgraph2 = {
10147+
name: 'Subgraph2',
10148+
typeDefs: gql`
10149+
interface I @key(fields: "id") {
10150+
id: ID!
10151+
name: String!
10152+
}
10153+
10154+
type A implements I @key(fields: "id") {
10155+
id: ID!
10156+
name: String!
10157+
x: X!
10158+
}
10159+
10160+
type X {
10161+
id: ID!
10162+
}
10163+
10164+
type B implements I @key(fields: "id") {
10165+
id: ID!
10166+
name: String!
10167+
y: Y!
10168+
}
10169+
10170+
type Y {
10171+
id: ID!
10172+
}
10173+
`,
10174+
};
10175+
10176+
const [api, queryPlanner] = composeAndCreatePlanner(subgraph1, subgraph2);
10177+
const operation = operationFromDocument(
10178+
api,
10179+
gql`
10180+
{
10181+
foo {
10182+
item {
10183+
__typename
10184+
id
10185+
... on A {
10186+
__typename
10187+
id
10188+
x {
10189+
__typename
10190+
id
10191+
}
10192+
}
10193+
... on B {
10194+
__typename
10195+
id
10196+
y {
10197+
__typename
10198+
id
10199+
}
10200+
}
10201+
}
10202+
}
10203+
}
10204+
`,
10205+
);
10206+
10207+
const plan = queryPlanner.buildQueryPlan(operation);
10208+
expect(plan).toMatchInlineSnapshot(`
10209+
QueryPlan {
10210+
Sequence {
10211+
Fetch(service: "Subgraph1") {
10212+
{
10213+
foo {
10214+
item {
10215+
__typename
10216+
id
10217+
}
10218+
}
10219+
}
10220+
},
10221+
Flatten(path: "foo.item") {
10222+
Fetch(service: "Subgraph2") {
10223+
{
10224+
... on I {
10225+
__typename
10226+
id
10227+
}
10228+
} =>
10229+
{
10230+
... on I {
10231+
__typename
10232+
... on A {
10233+
x {
10234+
__typename
10235+
id
10236+
}
10237+
}
10238+
... on B {
10239+
y {
10240+
__typename
10241+
id
10242+
}
10243+
}
10244+
}
10245+
}
10246+
},
10247+
},
10248+
},
10249+
}
10250+
`);
10251+
});

query-planner-js/src/buildPlan.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2612,8 +2612,12 @@ class FetchDependencyGraph {
26122612
}
26132613

26142614
private removeUselessGroups(group: FetchGroup) {
2615+
// Since we can potentially remove child in place, we need to
2616+
// explicitly copy the list of all children to ensure that we
2617+
// process ALL children
2618+
const children = group.children().concat();
26152619
// Recursing first, this makes it a bit easier to reason about.
2616-
for (const child of group.children()) {
2620+
for (const child of children) {
26172621
this.removeUselessGroups(child);
26182622
}
26192623

0 commit comments

Comments
 (0)