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

ApolloStore#writeAndPublish overwrites cache entries when using fragments #2818

Closed
mateuszkwiecinski opened this issue Dec 23, 2020 · 3 comments

Comments

@mateuszkwiecinski
Copy link
Contributor

mateuszkwiecinski commented Dec 23, 2020

Summary
When calling writeAndPublish for an operation that fetches part of the schema using graphql fragments, then the information saved in cache doesn't reflect passed operationData.

Version
2.4.6

Description
Context: We try to treat Apollo cache as a source of a truth, leveraing ApolloStore#writeAndPublish as a way to help Apollo cross-cache between queries or to i.e. fill incomplete data - basically we use it in edge cases, as a last resort.
I noticed that despite manually writing data to cache we still experience cache misses and unexpected api calls caused by a wrong cache state. I was able to narrow down the issue down to simple repro:

Given following schema:

type Query {
  home: Home!
}

type Home {
  sectionA: SectionA
}

type SectionA {
  id: String!
  name: String!
  imageUrl: String
}

and a following query:

query Home {
  home {
    ...sectionFragment
    sectionA {
      name
    }
  }
}

fragment sectionFragment on Home {
  sectionA {
    id
    imageUrl
  }
}

when following code invoked:

apolloStore.writeAndPublish( // or just `write(`
      HomeQuery(),
      HomeQuery.Data(
        HomeQuery.Home(
          sectionA = HomeQuery.SectionA(
            name = "section-name"
          ),
          fragments = HomeQuery.Home.Fragments(
            sectionFragment = SectionFragment(
              sectionA = SectionFragment.SectionA(
                id = "section-id",
                imageUrl = "https://...",
              ),
            )
          )
        )
      )
    ).execute()

then cache gets filled with:

LruNormalizedCache {
  "section-id" : {
    "__typename" : SectionA
    "id" : section-id
    "imageUrl" : https://...
  }

  "QUERY_ROOT" : {
    "home" : CacheRecordRef(home)
  }

  "home" : {
    "__typename" : Home
    "sectionA" : CacheRecordRef(section-id)
  }
}

Expected result:
I'd expect the name=section-name to be saved under "section-id" cache key.

  "section-id" : {
    "__typename" : SectionA
    "id" : section-id
    "imageUrl" : https://...
    "name" : section-name
  }

More context:

  • From what I understood the issue appears here:
    Screenshot 2020-12-24 at 00 25 26
    where both marshallers write different parts of an object under the same key, overwriting previously saved part
  • Obvious workaround would be to avoid using fragments if a query needs more fields than fragment already exposes.
  • I was surprised when discovered mentioned marshaller() mechanism isn't used with network responses and they work in an expected way.
@mateuszkwiecinski mateuszkwiecinski changed the title ApolloStore#writeAndPublish overwrites cache entries when using nested fragments ApolloStore#writeAndPublish overwrites cache entries when using fragments Dec 23, 2020
martinbonnin added a commit to martinbonnin/apollo-android-samples that referenced this issue Dec 24, 2020
@martinbonnin
Copy link
Contributor

Thanks for the very detailed bug report! I made a small reproducer there: https://github.com/martinbonnin/apollo-android-samples/blob/main/reproducers/2818-fragment-overwrites/src/main/kotlin/main.kt

A quick fix might be to do the merge in RealResponseWriter so that we do not overwrite the entries in buffer. The different values will come from different ResponseField but maybe it's not a big issue.

I was surprised when discovered mentioned marshaller() mechanism isn't used with network responses and they work in an expected way.

That's surprising indeed. @sav007 do you have more background how come caching a network response does not follow the same path?

@sav007
Copy link
Contributor

sav007 commented Dec 24, 2020

For network responses we don't have to follow the same path as with writing to store via marshallers because we build normalized cache records in place where we parse network response. Means by the time we parsed network response we have already cache records, so no need to build cache records again via generated model marshallers

martinbonnin added a commit that referenced this issue Jan 4, 2021
OperationResponseParser now doesn't do normalization anymore. Normalization is now done from the models, like it is done when using the cache write APIs.

Advantages:
- Normalizing network response now uses the same path as when writing to the cache using the imperative cache write API. This ensures the results will be identical (see #2818)
- Normalization can be better decoupled from the parsing and not applied at all if no cache is configured. This also means we can make normalization "async" and send a model to the UI without having to wait

Inconvenients:
- The whole parsing + normalization process is now slightly slower (~7%, see benchmarks) but I'm quite confident we can improve this by using the streaming parser and tweaking the normalization algorithms
@mateuszkwiecinski
Copy link
Contributor Author

Fixed in 3.0.0-rc03 together with #3672

@eduardb eduardb mentioned this issue Aug 25, 2022
martinbonnin pushed a commit that referenced this issue Aug 29, 2022
* Fix overwrites cache entries when using fragments

* Add integration test for fragment overwrites fix

* Avoid using method not available before API level 24 on Android

* Add assertion to check that the fragment is also correctly written
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants