diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index 9daee467aab..34e95ca6132 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -5,6 +5,7 @@ > The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the appropriate changes within that release will be moved into the new section. - The default branch of the repository has been changed to `main`. As this changed a number of references in the repository's `package.json` and `README.md` files (e.g., for badges, links, etc.), this necessitates a release to publish those changes to npm. [PR #4302](https://github.com/apollographql/apollo-server/pull/4302) +- __FIX__: The `mergeFieldNodeSelectionSets` method no longer mutates original FieldNode objects. Before, it was updating the selection set of the original object, corrupting the data accross requests. ## 0.16.9 diff --git a/packages/apollo-gateway/src/FieldSet.ts b/packages/apollo-gateway/src/FieldSet.ts index 7c792ea2a6b..25f613d3e80 100644 --- a/packages/apollo-gateway/src/FieldSet.ts +++ b/packages/apollo-gateway/src/FieldSet.ts @@ -151,13 +151,16 @@ function mergeFieldNodeSelectionSets( nonAliasedFieldNodes, ).values(), ).map((nodesWithSameName) => { - const node = nodesWithSameName[0]; + const node = { ...nodesWithSameName[0] }; if (node.selectionSet) { - node.selectionSet.selections = mergeFieldNodeSelectionSets( - nodesWithSameName.flatMap( - (node) => node.selectionSet?.selections || [], + node.selectionSet = { + ...node.selectionSet, + selections: mergeFieldNodeSelectionSets( + nodesWithSameName.flatMap( + (node) => node.selectionSet?.selections || [], + ), ), - ); + }; } return node; }); diff --git a/packages/apollo-gateway/src/__tests__/gateway/queryPlanCache.test.ts b/packages/apollo-gateway/src/__tests__/gateway/queryPlanCache.test.ts index 247ee4dd7a2..abb692c393a 100644 --- a/packages/apollo-gateway/src/__tests__/gateway/queryPlanCache.test.ts +++ b/packages/apollo-gateway/src/__tests__/gateway/queryPlanCache.test.ts @@ -104,3 +104,118 @@ it('supports multiple operations and operationName', async () => { ], }); }); + +it('does not corrupt cached queryplan data across requests', async () => { + const serviceA = { + name: 'a', + typeDefs: gql` + type Query { + user: User + } + + type User @key(fields: "id") { + id: ID! + preferences: Preferences + } + + type Preferences { + favorites: Things + } + + type Things { + color: String + animal: String + } + `, + resolvers: { + Query: { + user() { + return { + id: '1', + preferences: { + favorites: { color: 'limegreen', animal: 'platypus' }, + }, + }; + }, + }, + }, + }; + + const serviceB = { + name: 'b', + typeDefs: gql` + extend type User @key(fields: "id") { + id: ID! @external + preferences: Preferences @external + favoriteColor: String + @requires(fields: "preferences { favorites { color } }") + favoriteAnimal: String + @requires(fields: "preferences { favorites { animal } }") + } + + extend type Preferences { + favorites: Things @external + } + + extend type Things { + color: String @external + animal: String @external + } + `, + resolvers: { + User: { + favoriteColor(user: any) { + return user.preferences.favorites.color; + }, + favoriteAnimal(user: any) { + return user.preferences.favorites.animal; + }, + }, + }, + }; + + const gateway = new ApolloGateway({ + localServiceList: [serviceA, serviceB], + buildService: service => { + return new LocalGraphQLDataSource(buildFederatedSchema([service])); + }, + }); + + const { schema, executor } = await gateway.load(); + + const server = new ApolloServer({ schema, executor }); + + const call = createTestClient(server); + + const query1 = `#graphql + query UserFavoriteColor { + user { + favoriteColor + } + } + `; + + const query2 = `#graphql + query UserFavorites { + user { + favoriteColor + favoriteAnimal + } + } + `; + + const result1 = await call.query({ + query: query1, + }); + const result2 = await call.query({ + query: query2, + }); + const result3 = await call.query({ + query: query1, + }); + + expect(result1.errors).toEqual(undefined); + expect(result2.errors).toEqual(undefined); + expect(result3.errors).toEqual(undefined); + expect(result1).toEqual(result3); +}); diff --git a/packages/apollo-gateway/src/__tests__/integration/requires.test.ts b/packages/apollo-gateway/src/__tests__/integration/requires.test.ts index d4a35bf0fb6..25e940a9a23 100644 --- a/packages/apollo-gateway/src/__tests__/integration/requires.test.ts +++ b/packages/apollo-gateway/src/__tests__/integration/requires.test.ts @@ -235,7 +235,6 @@ it('collapses nested requires with user-defined fragments', async () => { preferences { favorites { animal - color } } }