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

Prevent incorrect result merging when aliasing __typename or id. #10789

Merged
merged 10 commits into from
May 9, 2023

Conversation

phryneas
Copy link
Member

@phryneas phryneas commented Apr 21, 2023

This fixes the first of two bugs pointed out in #10784.

This is just a draft PR, not the finished result, and tons of tests fill fail right now. I got the correct behavior in for this case, but broke a ton of other stuff in the meantime and will need to work on that.

Just opening the PR for transparency here :)

Checklist:

  • If this PR contains changes to the library itself (not necessary for e.g. docs updates), please include a changeset (see CONTRIBUTING.md)
  • If this PR is a new feature, please reference an issue where a consensus about the design was reached (not necessary for small changes)
  • Make sure all of the significant new logic is covered by tests

@changeset-bot
Copy link

changeset-bot bot commented Apr 21, 2023

🦋 Changeset detected

Latest commit: f27932d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@apollo/client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@phryneas
Copy link
Member Author

/release:pr

@github-actions
Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/client@3.7.12.

@phryneas
Copy link
Member Author

/release:pr

@github-actions
Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/client@0.0.0-pr-10789-20230427152943.

@phryneas phryneas changed the title Fix cache poisoning bug. Prevent incorrect result merging when aliasing __typename or id. Apr 28, 2023
@phryneas phryneas marked this pull request as ready for review April 28, 2023 09:54
Copy link
Member

@benjamn benjamn left a comment

Choose a reason for hiding this comment

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

Thanks for tracking down and fixing these (hopefully) last few aliasing sensitivities! I would like to see the loop optimization from my comment implemented (however you see fit), but this looks good to me.

Comment on lines 302 to 303
for (const selection of selectionSet.selections) {
if (!isField(selection)) {
Copy link
Member

Choose a reason for hiding this comment

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

I have a feeling getTypenameFromResult is (or could be) hot code, since we need to know the __typename for every result object, and each call might involve recursive fragment traversal.

Instead of iterating over the selections again here, here's one way to avoid the whole second loop when there are no fragments in a given selection set:

diff --git a/src/utilities/graphql/storeUtils.ts b/src/utilities/graphql/storeUtils.ts
index f995b41d9..f2ddec155 100644
--- a/src/utilities/graphql/storeUtils.ts
+++ b/src/utilities/graphql/storeUtils.ts
@@ -18,6 +18,7 @@ import {
   NameNode,
   SelectionSetNode,
   DocumentNode,
+  FragmentSpreadNode,
 } from 'graphql';
 
 import { isNonNullObject } from '../common/objects';
@@ -289,18 +290,26 @@ export function getTypenameFromResult(
   selectionSet: SelectionSetNode,
   fragmentMap?: FragmentMap,
 ): string | undefined {
+  let fragments: undefined | Array<InlineFragmentNode | FragmentSpreadNode>;
+
   for (const selection of selectionSet.selections) {
     if (isField(selection)) {
       if (selection.name.value === '__typename') {
         return result[resultKeyNameFromField(selection)];
       }
+    } else if (fragments) {
+      fragments.push(selection);
+    } else {
+      fragments = [selection];
     }
   }
+
   if (typeof result.__typename === 'string') {
     return result.__typename;
   }
-  for (const selection of selectionSet.selections) {
-    if (!isField(selection)) {
+
+  if (fragments) {
+    for (const selection of fragments) {
       const typename = getTypenameFromResult(
         result,
         getFragmentFromSelection(selection, fragmentMap)!.selectionSet,

const specifierOrId = keyFn(object, context);
const specifierOrId = keyFn({...object, ...storeObject}, context);
Copy link
Member

Choose a reason for hiding this comment

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

Great catch @phryneas!

For future reference, I think this should have been keyFn(storeObject, context) from the beginning, but our test suite has a few test cases where properties of a result object were important (like id) even if not mentioned as fields in the query document, so keyFn({...object, ...storeObject}, context) ensures backwards compatibility for that edge case, while always preferring storeObject properties over object properties.

@netlify
Copy link

netlify bot commented May 9, 2023

Deploy Preview for apollo-client-docs ready!

Name Link
🔨 Latest commit f27932d
🔍 Latest deploy log https://app.netlify.com/sites/apollo-client-docs/deploys/645a44d54053a000087f8447
😎 Deploy Preview https://deploy-preview-10789--apollo-client-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants