Skip to content

Commit 02eab3a

Browse files
author
Sylvain Lebresne
authored
Fix incorrect removal of fragments only used by other fragments (#2648)
The patch from #2628, while it did fix the issue of leaving unused fragments, mistakenly introduced a regression in that fragments that are used by other fragments but are not used in the query selection are now removed, which is obviously undesirable. This was simply because the patch of #2628 only counted the usages in the main selection, not the ones other fragments as it should have. This commit fixes that.
1 parent d4bf1fb commit 02eab3a

3 files changed

Lines changed: 134 additions & 0 deletions

File tree

.changeset/stale-birds-think.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@apollo/federation-internals": patch
3+
---
4+
5+
Fix regression in named fragment reuse introduced by 2.4.8 that caused fragments that were only used by other fragments
6+
to not be reused, even if they are making the overall query smaller and thus should be reused.
7+

internals-js/src/__tests__/operations.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,6 +1904,122 @@ describe('fragments optimization', () => {
19041904
}
19051905
`);
19061906
});
1907+
1908+
test('keeps fragments only used by other fragments (if they are used enough times)', () => {
1909+
const schema = parseSchema(`
1910+
type Query {
1911+
t1: T
1912+
t2: T
1913+
}
1914+
1915+
type T {
1916+
a1: Int
1917+
a2: Int
1918+
b1: B
1919+
b2: B
1920+
}
1921+
1922+
type B {
1923+
x: Int
1924+
y: Int
1925+
}
1926+
`);
1927+
const gqlSchema = schema.toGraphQLJSSchema();
1928+
1929+
const operation = parseOperation(schema, `
1930+
query {
1931+
t1 {
1932+
...TFields
1933+
}
1934+
t2 {
1935+
...TFields
1936+
}
1937+
}
1938+
1939+
fragment TFields on T {
1940+
...DirectFieldsOfT
1941+
b1 {
1942+
...BFields
1943+
}
1944+
b2 {
1945+
...BFields
1946+
}
1947+
}
1948+
1949+
fragment DirectFieldsOfT on T {
1950+
a1
1951+
a2
1952+
}
1953+
1954+
fragment BFields on B {
1955+
x
1956+
y
1957+
}
1958+
`);
1959+
expect(validate(gqlSchema, parse(operation.toString()))).toStrictEqual([]);
1960+
1961+
const withoutFragments = operation.expandAllFragments();
1962+
expect(withoutFragments.toString()).toMatchString(`
1963+
{
1964+
t1 {
1965+
a1
1966+
a2
1967+
b1 {
1968+
x
1969+
y
1970+
}
1971+
b2 {
1972+
x
1973+
y
1974+
}
1975+
}
1976+
t2 {
1977+
a1
1978+
a2
1979+
b1 {
1980+
x
1981+
y
1982+
}
1983+
b2 {
1984+
x
1985+
y
1986+
}
1987+
}
1988+
}
1989+
`);
1990+
1991+
const optimized = withoutFragments.optimize(operation.fragments!, 2);
1992+
expect(validate(gqlSchema, parse(optimized.toString()))).toStrictEqual([]);
1993+
1994+
// The `DirectFieldsOfT` fragments should not be kept as it is used only once within `TFields`,
1995+
// but the `BFields` one should be kept.
1996+
expect(optimized.toString()).toMatchString(`
1997+
fragment BFields on B {
1998+
x
1999+
y
2000+
}
2001+
2002+
fragment TFields on T {
2003+
a1
2004+
a2
2005+
b1 {
2006+
...BFields
2007+
}
2008+
b2 {
2009+
...BFields
2010+
}
2011+
}
2012+
2013+
{
2014+
t1 {
2015+
...TFields
2016+
}
2017+
t2 {
2018+
...TFields
2019+
}
2020+
}
2021+
`);
2022+
});
19072023
});
19082024

19092025
describe('validations', () => {

internals-js/src/operations.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,9 @@ export class Operation {
946946
// refactor all this later to avoid this case without additional complexity.
947947
if (finalFragments) {
948948
const usages = new Map<string, number>();
949+
// Collecting all usages, both in the selection and within other fragments.
949950
optimizedSelection.collectUsedFragmentNames(usages);
951+
finalFragments.collectUsedFragmentNames(usages);
950952
finalFragments = finalFragments.filter((f) => (usages.get(f.name) ?? 0) > 0);
951953
}
952954
}
@@ -1284,6 +1286,15 @@ export class NamedFragments {
12841286
return this.fragments.values();
12851287
}
12861288

1289+
/**
1290+
* Collect the usages of fragments that are used within the selection of other fragments.
1291+
*/
1292+
collectUsedFragmentNames(collector: Map<string, number>) {
1293+
for (const fragment of this.definitions()) {
1294+
fragment.collectUsedFragmentNames(collector);
1295+
}
1296+
}
1297+
12871298
map(mapper: (def: NamedFragmentDefinition) => NamedFragmentDefinition): NamedFragments {
12881299
const mapped = new NamedFragments();
12891300
for (const def of this.fragments.values()) {

0 commit comments

Comments
 (0)