apollographql / apollo-kotlin

:rocket:  A strongly-typed, caching GraphQL client for the JVM, Android, and Kotlin multiplatform.
https://www.apollographql.com/docs/kotlin
MIT License
3.71k stars 652 forks source link

Normalized cache: handle dangling references #2507

Open piotr-pawlowski opened 3 years ago

piotr-pawlowski commented 3 years ago

ApolloStore: does remove(key: CacheKey) method remove ApolloCacheReference field in related records? I mean a mechanism like "ON DELETE CASCADE SET NULL" in RDBMS.

martinbonnin commented 3 years ago

There is ApolloStore.remove(cacheKey: CacheKey, cascade: Boolean). Would that work?

piotr-pawlowski commented 3 years ago

This method with flag cascade = true looks for children and deletes them with root record (cacheKey).

From SqlNormalizedCache:

 override fun remove(cacheKey: CacheKey, cascade: Boolean): Boolean {
    val result: Boolean = nextCache?.remove(cacheKey, cascade) ?: false
    if (result) {
      return true
    }
    return if (cascade) {
      selectRecordForKey(cacheKey.key)
          ?.referencedFields()
          ?.all { remove(CacheKey(it.key), cascade = true) }
          ?: false
    } else {
      deleteRecord(cacheKey.key)
    }
  }

From cache.sq:

recordForKey:
SELECT key, record FROM records WHERE key=?;

So this method loads the record from database, retrieves referencedFields (children for that loaded record) and deletes them all with given record.

But what about referencing field? There should be a solution to remove references from parent records to provide consistency of database.

For example:

SELECT key, record FROM records WHERE record LIKE '%ApolloCacheReference{<cache key>}%'

And then clear that referencing field from the record by merge method or somehow. Without that solution there is no way to fetch parent data from cache, because there occurs MISS field error (there is still ApolloCacheReference field to a child record that doesn't exists in database).

martinbonnin commented 2 years ago

Hoping to dig into this soon. I'm realizing just now that cascade wasn't a great naming choice as it's about deleting children, not parents like SQL is doing.

apollo-client refers to this as dangling references and handles them with readFunctions.

I think something similar would work. Compared to cascade delete, that'd keep the dangling reference around in case it becomes valid again. I think that could be handy?

I tweaked the name of this issue a bit so that it's more clear in https://github.com/apollographql/apollo-kotlin/issues/2331