From eec192e4dac5935b6affd21068a0e6aba5f6e9b7 Mon Sep 17 00:00:00 2001 From: BoD Date: Mon, 7 Apr 2025 10:14:33 +0200 Subject: [PATCH 1/5] Different roots for Query, Mutation, Subscription --- .../api/normalized-cache-incubating.api | 9 +-- .../api/normalized-cache-incubating.klib.api | 8 ++- .../cache/normalized/ApolloStore.kt | 2 +- .../cache/normalized/GarbageCollection.kt | 2 +- .../cache/normalized/api/CacheKey.kt | 25 +++++--- .../normalized/internal/CacheBatchReader.kt | 3 +- .../normalized/internal/DefaultApolloStore.kt | 5 +- .../cache/normalized/internal/Normalizer.kt | 11 ++-- .../normalized/internal/WatcherInterceptor.kt | 15 ++++- .../normalized/sql/SqlNormalizedCacheTest.kt | 2 +- .../src/commonTest/kotlin/DoNotStoreTest.kt | 4 +- tests/defer/build.gradle.kts | 1 + .../kotlin/test/DeferNormalizedCacheTest.kt | 14 ++--- tests/normalization-tests/build.gradle.kts | 6 ++ .../commonMain/graphql/4/operations.graphql | 15 +++++ .../src/commonMain/graphql/4/schema.graphqls | 25 ++++++++ .../kotlin/com/example/NormalizationTest.kt | 60 +++++++++++++++++++ .../src/commonTest/kotlin/FetchPolicyTest.kt | 2 +- .../src/commonTest/kotlin/NormalizerTest.kt | 20 +++---- .../kotlin/WriteToCacheAsynchronouslyTest.kt | 2 +- 20 files changed, 180 insertions(+), 51 deletions(-) create mode 100644 tests/normalization-tests/src/commonMain/graphql/4/operations.graphql create mode 100644 tests/normalization-tests/src/commonMain/graphql/4/schema.graphqls diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.api b/normalized-cache-incubating/api/normalized-cache-incubating.api index ef82d5cb..af048e23 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.api @@ -240,14 +240,15 @@ public final class com/apollographql/cache/normalized/api/CacheKey { public final fun getKey ()Ljava/lang/String; public fun hashCode ()I public static fun hashCode-impl (Ljava/lang/String;)I - public static final fun rootKey-mqw0cJ0 ()Ljava/lang/String; public fun toString ()Ljava/lang/String; public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; public final synthetic fun unbox-impl ()Ljava/lang/String; } public final class com/apollographql/cache/normalized/api/CacheKey$Companion { - public final fun rootKey-mqw0cJ0 ()Ljava/lang/String; + public final fun getMUTATION_ROOT-mqw0cJ0 ()Ljava/lang/String; + public final fun getQUERY_ROOT-mqw0cJ0 ()Ljava/lang/String; + public final fun getSUBSCRIPTION_ROOT-mqw0cJ0 ()Ljava/lang/String; } public abstract interface class com/apollographql/cache/normalized/api/CacheKeyGenerator { @@ -260,10 +261,6 @@ public final class com/apollographql/cache/normalized/api/CacheKeyGeneratorConte public final fun getVariables ()Lcom/apollographql/apollo/api/Executable$Variables; } -public final class com/apollographql/cache/normalized/api/CacheKeyKt { - public static final fun isRootKey-pWl1Des (Ljava/lang/String;)Z -} - public abstract class com/apollographql/cache/normalized/api/CacheKeyResolver : com/apollographql/cache/normalized/api/CacheResolver { public fun ()V public abstract fun cacheKeyForField-fLoEQYY (Lcom/apollographql/cache/normalized/api/ResolverContext;)Ljava/lang/String; diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api index 73743aac..94144144 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api @@ -483,7 +483,12 @@ final value class com.apollographql.cache.normalized.api/CacheKey { // com.apoll final fun toString(): kotlin/String // com.apollographql.cache.normalized.api/CacheKey.toString|toString(){}[0] final object Companion { // com.apollographql.cache.normalized.api/CacheKey.Companion|null[0] - final fun rootKey(): com.apollographql.cache.normalized.api/CacheKey // com.apollographql.cache.normalized.api/CacheKey.Companion.rootKey|rootKey(){}[0] + final val MUTATION_ROOT // com.apollographql.cache.normalized.api/CacheKey.Companion.MUTATION_ROOT|{}MUTATION_ROOT[0] + final fun (): com.apollographql.cache.normalized.api/CacheKey // com.apollographql.cache.normalized.api/CacheKey.Companion.MUTATION_ROOT.|(){}[0] + final val QUERY_ROOT // com.apollographql.cache.normalized.api/CacheKey.Companion.QUERY_ROOT|{}QUERY_ROOT[0] + final fun (): com.apollographql.cache.normalized.api/CacheKey // com.apollographql.cache.normalized.api/CacheKey.Companion.QUERY_ROOT.|(){}[0] + final val SUBSCRIPTION_ROOT // com.apollographql.cache.normalized.api/CacheKey.Companion.SUBSCRIPTION_ROOT|{}SUBSCRIPTION_ROOT[0] + final fun (): com.apollographql.cache.normalized.api/CacheKey // com.apollographql.cache.normalized.api/CacheKey.Companion.SUBSCRIPTION_ROOT.|(){}[0] } } @@ -567,7 +572,6 @@ final val com.apollographql.cache.normalized/isFromCache // com.apollographql.ca final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/logCacheMisses(kotlin/Function1 = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/logCacheMisses|logCacheMisses@com.apollographql.apollo.ApolloClient.Builder(kotlin.Function1){}[0] final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/normalizedCache(com.apollographql.cache.normalized.api/NormalizedCacheFactory, com.apollographql.cache.normalized.api/CacheKeyGenerator = ..., com.apollographql.cache.normalized.api/MetadataGenerator = ..., com.apollographql.cache.normalized.api/CacheResolver = ..., com.apollographql.cache.normalized.api/RecordMerger = ..., com.apollographql.cache.normalized.api/FieldKeyGenerator = ..., com.apollographql.cache.normalized.api/EmbeddedFieldsProvider = ..., com.apollographql.cache.normalized.api/MaxAgeProvider = ..., kotlin/Boolean = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/normalizedCache|normalizedCache@com.apollographql.apollo.ApolloClient.Builder(com.apollographql.cache.normalized.api.NormalizedCacheFactory;com.apollographql.cache.normalized.api.CacheKeyGenerator;com.apollographql.cache.normalized.api.MetadataGenerator;com.apollographql.cache.normalized.api.CacheResolver;com.apollographql.cache.normalized.api.RecordMerger;com.apollographql.cache.normalized.api.FieldKeyGenerator;com.apollographql.cache.normalized.api.EmbeddedFieldsProvider;com.apollographql.cache.normalized.api.MaxAgeProvider;kotlin.Boolean){}[0] final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/store(com.apollographql.cache.normalized/ApolloStore, kotlin/Boolean = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/store|store@com.apollographql.apollo.ApolloClient.Builder(com.apollographql.cache.normalized.ApolloStore;kotlin.Boolean){}[0] -final fun (com.apollographql.cache.normalized.api/CacheKey).com.apollographql.cache.normalized.api/isRootKey(): kotlin/Boolean // com.apollographql.cache.normalized.api/isRootKey|isRootKey@com.apollographql.cache.normalized.api.CacheKey(){}[0] final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/allRecords(): kotlin.collections/Map // com.apollographql.cache.normalized/allRecords|allRecords@com.apollographql.cache.normalized.api.NormalizedCache(){}[0] final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/garbageCollect(com.apollographql.cache.normalized.api/MaxAgeProvider, kotlin.time/Duration = ...): com.apollographql.cache.normalized/GarbageCollectResult // com.apollographql.cache.normalized/garbageCollect|garbageCollect@com.apollographql.cache.normalized.api.NormalizedCache(com.apollographql.cache.normalized.api.MaxAgeProvider;kotlin.time.Duration){}[0] final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/removeDanglingReferences(): com.apollographql.cache.normalized/RemovedFieldsAndRecords // com.apollographql.cache.normalized/removeDanglingReferences|removeDanglingReferences@com.apollographql.cache.normalized.api.NormalizedCache(){}[0] diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ApolloStore.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ApolloStore.kt index f2fc4d79..0ece78f2 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ApolloStore.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ApolloStore.kt @@ -259,7 +259,7 @@ interface ApolloStore { fun normalize( executable: Executable, dataWithErrors: DataWithErrors, - rootKey: CacheKey = CacheKey.rootKey(), + rootKey: CacheKey = CacheKey.QUERY_ROOT, customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty, ): Map diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt index 3f99c737..ef670d0c 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt @@ -30,7 +30,7 @@ fun Map.getReachableCacheKeys(): Set { } return mutableSetOf().also { reachableCacheKeys -> - getReachableCacheKeys(listOf(CacheKey.rootKey()), reachableCacheKeys) + getReachableCacheKeys(listOf(CacheKey.QUERY_ROOT, CacheKey.MUTATION_ROOT, CacheKey.SUBSCRIPTION_ROOT), reachableCacheKeys) } } diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheKey.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheKey.kt index 37aeede6..a9a59290 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheKey.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheKey.kt @@ -1,7 +1,10 @@ package com.apollographql.cache.normalized.api +import com.apollographql.apollo.api.Mutation +import com.apollographql.apollo.api.Operation +import com.apollographql.apollo.api.Query +import com.apollographql.apollo.api.Subscription import kotlin.jvm.JvmInline -import kotlin.jvm.JvmStatic /** * A [CacheKey] identifies an object in the cache. @@ -42,17 +45,14 @@ value class CacheKey( override fun toString() = "CacheKey(${keyToString()})" companion object { - private val ROOT_CACHE_KEY = CacheKey("QUERY_ROOT") - - @JvmStatic - fun rootKey(): CacheKey { - return ROOT_CACHE_KEY - } + val QUERY_ROOT = CacheKey("QUERY_ROOT") + val MUTATION_ROOT = CacheKey("MUTATION_ROOT") + val SUBSCRIPTION_ROOT = CacheKey("SUBSCRIPTION_ROOT") } } -fun CacheKey.isRootKey(): Boolean { - return this == CacheKey.rootKey() +internal fun CacheKey.isRootKey(): Boolean { + return this == CacheKey.QUERY_ROOT || this == CacheKey.MUTATION_ROOT || this == CacheKey.SUBSCRIPTION_ROOT } internal fun CacheKey.fieldKey(fieldName: String): String { @@ -66,3 +66,10 @@ internal fun CacheKey.append(vararg keys: String): CacheKey { } return cacheKey } + +internal fun Operation<*>.rootKey() = when (this) { + is Query -> CacheKey.QUERY_ROOT + is Mutation -> CacheKey.MUTATION_ROOT + is Subscription -> CacheKey.SUBSCRIPTION_ROOT + else -> throw IllegalArgumentException("Unknown operation type: ${this::class}") +} diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/CacheBatchReader.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/CacheBatchReader.kt index b5a3f2b7..ab79ee6d 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/CacheBatchReader.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/CacheBatchReader.kt @@ -15,6 +15,7 @@ import com.apollographql.cache.normalized.api.FieldKeyGenerator import com.apollographql.cache.normalized.api.ReadOnlyNormalizedCache import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.api.ResolverContext +import com.apollographql.cache.normalized.api.isRootKey import com.apollographql.cache.normalized.cacheMissException import kotlin.jvm.JvmSuppressWildcards @@ -121,7 +122,7 @@ internal class CacheBatchReader( copy.forEach { pendingReference -> var record = records[pendingReference.key] if (record == null) { - if (pendingReference.key == CacheKey.rootKey()) { + if (pendingReference.key.isRootKey()) { // This happens the very first time we read the cache record = Record(pendingReference.key, emptyMap()) } else { diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/DefaultApolloStore.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/DefaultApolloStore.kt index 9df925f8..d4e4e99b 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/DefaultApolloStore.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/DefaultApolloStore.kt @@ -27,6 +27,7 @@ import com.apollographql.cache.normalized.api.NormalizedCacheFactory import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.api.RecordMerger import com.apollographql.cache.normalized.api.propagateErrors +import com.apollographql.cache.normalized.api.rootKey import com.apollographql.cache.normalized.api.withErrors import com.apollographql.cache.normalized.cacheHeaders import com.apollographql.cache.normalized.cacheInfo @@ -132,7 +133,7 @@ internal class DefaultApolloStore( cacheHeaders = cacheHeaders, cacheResolver = cacheResolver, variables = variables, - rootKey = CacheKey.rootKey(), + rootKey = operation.rootKey(), rootSelections = operation.rootField().selections, rootField = operation.rootField(), fieldKeyGenerator = fieldKeyGenerator, @@ -225,6 +226,7 @@ internal class DefaultApolloStore( val records = normalize( executable = operation, dataWithErrors = dataWithErrors, + rootKey = operation.rootKey(), customScalarAdapters = customScalarAdapters, ).values.toSet() return cache.merge(records, cacheHeaders, recordMerger) @@ -257,6 +259,7 @@ internal class DefaultApolloStore( val records = normalize( executable = operation, dataWithErrors = dataWithErrors, + rootKey = operation.rootKey(), customScalarAdapters = customScalarAdapters, ).values.map { record -> Record( diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/Normalizer.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/Normalizer.kt index 6f155b54..b15ce5ff 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/Normalizer.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/Normalizer.kt @@ -31,7 +31,6 @@ import com.apollographql.cache.normalized.api.MetadataGeneratorContext import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.api.TypePolicyCacheKeyGenerator import com.apollographql.cache.normalized.api.append -import com.apollographql.cache.normalized.api.isRootKey import com.apollographql.cache.normalized.api.toMaxAgeField import com.apollographql.cache.normalized.api.withErrors import kotlin.time.Duration @@ -107,8 +106,9 @@ internal class Normalizer( val fieldKey = fieldKeyGenerator.getFieldKey(FieldKeyContext(parentType.name, mergedField, variables)) - val base = if (key.isRootKey()) { - // If we're at the root level, skip `QUERY_ROOT` altogether to save a few bytes + val base = if (key == CacheKey.QUERY_ROOT) { + // If we're at the query root level, skip `QUERY_ROOT` altogether to save a few bytes. + // For mutations and subscriptions, keep it. null } else { key @@ -194,6 +194,7 @@ internal class Normalizer( embeddedFields: List, ): Any? { val field = fieldPath.last() + /** * Remove the NotNull decoration if needed */ @@ -281,7 +282,7 @@ internal class Normalizer( */ fun D.normalized( executable: Executable, - rootKey: CacheKey = CacheKey.rootKey(), + rootKey: CacheKey = CacheKey.QUERY_ROOT, customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty, cacheKeyGenerator: CacheKeyGenerator = TypePolicyCacheKeyGenerator, metadataGenerator: MetadataGenerator = EmptyMetadataGenerator, @@ -298,7 +299,7 @@ fun D.normalized( */ fun DataWithErrors.normalized( executable: Executable, - rootKey: CacheKey = CacheKey.rootKey(), + rootKey: CacheKey = CacheKey.QUERY_ROOT, customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty, cacheKeyGenerator: CacheKeyGenerator = TypePolicyCacheKeyGenerator, metadataGenerator: MetadataGenerator = EmptyMetadataGenerator, diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/WatcherInterceptor.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/WatcherInterceptor.kt index 11e683e5..c63bd1d6 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/WatcherInterceptor.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/WatcherInterceptor.kt @@ -10,6 +10,7 @@ import com.apollographql.apollo.interceptor.ApolloInterceptor import com.apollographql.apollo.interceptor.ApolloInterceptorChain import com.apollographql.cache.normalized.ApolloStore import com.apollographql.cache.normalized.ApolloStoreInterceptor +import com.apollographql.cache.normalized.api.CacheKey import com.apollographql.cache.normalized.api.dependentKeys import com.apollographql.cache.normalized.api.withErrors import com.apollographql.cache.normalized.watchContext @@ -39,7 +40,12 @@ internal class WatcherInterceptor(val store: ApolloStore) : ApolloInterceptor, A var watchedKeys: Set? = watchContext.data?.let { data -> val dataWithErrors = (data as D).withErrors(request.operation, null, customScalarAdapters) - store.normalize(request.operation, dataWithErrors, customScalarAdapters = customScalarAdapters).values.dependentKeys() + store.normalize( + executable = request.operation, + dataWithErrors = dataWithErrors, + rootKey = CacheKey.QUERY_ROOT, + customScalarAdapters = customScalarAdapters, + ).values.dependentKeys() } return (store.changedKeys as SharedFlow) @@ -60,7 +66,12 @@ internal class WatcherInterceptor(val store: ApolloStore) : ApolloInterceptor, A if (response.data != null) { val dataWithErrors = response.data!!.withErrors(request.operation, response.errors, customScalarAdapters) watchedKeys = - store.normalize(request.operation, dataWithErrors, customScalarAdapters = customScalarAdapters).values.dependentKeys() + store.normalize( + executable = request.operation, + dataWithErrors = dataWithErrors, + rootKey = CacheKey.QUERY_ROOT, + customScalarAdapters = customScalarAdapters, + ).values.dependentKeys() } } } diff --git a/normalized-cache-sqlite-incubating/src/commonTest/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheTest.kt b/normalized-cache-sqlite-incubating/src/commonTest/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheTest.kt index b8835714..64b4285b 100644 --- a/normalized-cache-sqlite-incubating/src/commonTest/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheTest.kt +++ b/normalized-cache-sqlite-incubating/src/commonTest/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheTest.kt @@ -316,6 +316,6 @@ class SqlNormalizedCacheTest { companion object { val STANDARD_KEY = CacheKey("key") - val QUERY_ROOT_KEY = CacheKey.rootKey() + val QUERY_ROOT_KEY = CacheKey.QUERY_ROOT } } diff --git a/tests/cache-control/src/commonTest/kotlin/DoNotStoreTest.kt b/tests/cache-control/src/commonTest/kotlin/DoNotStoreTest.kt index d5e28151..569328f5 100644 --- a/tests/cache-control/src/commonTest/kotlin/DoNotStoreTest.kt +++ b/tests/cache-control/src/commonTest/kotlin/DoNotStoreTest.kt @@ -222,11 +222,11 @@ class DoNotStoreTest { ) apolloClient.apolloStore.accessCache { cache -> - val authRecord = cache.loadRecord(CacheKey("auth"), CacheHeaders.NONE)!! + val authRecord = cache.loadRecord(CacheKey.MUTATION_ROOT.append("auth"), CacheHeaders.NONE)!! // No password in field key assertContentEquals(listOf("signIn"), authRecord.fields.keys) - val signInRecord = cache.loadRecord(CacheKey("auth").append("signIn"), CacheHeaders.NONE)!! + val signInRecord = cache.loadRecord(CacheKey.MUTATION_ROOT.append("auth", "signIn"), CacheHeaders.NONE)!! // No token in record assertContentEquals(listOf("userData"), signInRecord.fields.keys) } diff --git a/tests/defer/build.gradle.kts b/tests/defer/build.gradle.kts index 78861f55..a53202d6 100644 --- a/tests/defer/build.gradle.kts +++ b/tests/defer/build.gradle.kts @@ -32,5 +32,6 @@ kotlin { apollo { service("base") { packageName.set("defer") + generateFragmentImplementations.set(true) } } diff --git a/tests/defer/src/commonTest/kotlin/test/DeferNormalizedCacheTest.kt b/tests/defer/src/commonTest/kotlin/test/DeferNormalizedCacheTest.kt index 061262b5..950fef34 100644 --- a/tests/defer/src/commonTest/kotlin/test/DeferNormalizedCacheTest.kt +++ b/tests/defer/src/commonTest/kotlin/test/DeferNormalizedCacheTest.kt @@ -36,6 +36,7 @@ import defer.SimpleDeferQuery import defer.WithFragmentSpreadsMutation import defer.WithFragmentSpreadsQuery import defer.fragment.ComputerFields +import defer.fragment.ComputerFieldsImpl import defer.fragment.ScreenFields import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -496,16 +497,13 @@ class DeferNormalizedCacheTest { assertEquals(networkExpected, networkActual) // Now cache is not empty - val cacheActual = apolloClient.query(WithFragmentSpreadsQuery()).fetchPolicy(FetchPolicy.CacheOnly).execute().dataOrThrow() + val cacheActual = store.readFragment(ComputerFieldsImpl(), CacheKey.MUTATION_ROOT.append("computers", "0")).data + println(cacheActual) // We get the last/fully formed data - val cacheExpected = WithFragmentSpreadsQuery.Data( - listOf(WithFragmentSpreadsQuery.Computer("Computer", "Computer1", ComputerFields("386", 1993, - ComputerFields.Screen("Screen", "640x480", - ScreenFields(false) - ) - ) - ) + val cacheExpected = ComputerFields("386", 1993, + ComputerFields.Screen("Screen", "640x480", + ScreenFields(false) ) ) assertEquals(cacheExpected, cacheActual) diff --git a/tests/normalization-tests/build.gradle.kts b/tests/normalization-tests/build.gradle.kts index 79bacc89..45733081 100644 --- a/tests/normalization-tests/build.gradle.kts +++ b/tests/normalization-tests/build.gradle.kts @@ -16,6 +16,7 @@ kotlin { dependencies { implementation(libs.apollo.runtime) implementation("com.apollographql.cache:normalized-cache-incubating") + implementation("com.apollographql.cache:normalized-cache-sqlite-incubating") } } @@ -24,6 +25,7 @@ kotlin { implementation("com.apollographql.cache:test-utils") implementation(libs.apollo.mockserver) implementation(libs.kotlin.test) + implementation("com.apollographql.cache:test-utils") } } @@ -48,4 +50,8 @@ apollo { srcDir("src/commonMain/graphql/3") packageName.set("com.example.three") } + service("4") { + srcDir("src/commonMain/graphql/4") + packageName.set("com.example.four") + } } diff --git a/tests/normalization-tests/src/commonMain/graphql/4/operations.graphql b/tests/normalization-tests/src/commonMain/graphql/4/operations.graphql new file mode 100644 index 00000000..b9b403be --- /dev/null +++ b/tests/normalization-tests/src/commonMain/graphql/4/operations.graphql @@ -0,0 +1,15 @@ +mutation CreateUserMutation($name: String!) { + createUser(name: $name) { + id + name + projects { + id + name + description + owner { + id + name + } + } + } +} diff --git a/tests/normalization-tests/src/commonMain/graphql/4/schema.graphqls b/tests/normalization-tests/src/commonMain/graphql/4/schema.graphqls new file mode 100644 index 00000000..632852d7 --- /dev/null +++ b/tests/normalization-tests/src/commonMain/graphql/4/schema.graphqls @@ -0,0 +1,25 @@ +type Schema { + query: Query + mutation: Mutation +} + +type Query { + user(id: ID!): User +} + +type Mutation { + createUser(name: String!): User! +} + +type User { + id: ID! + name: String! + projects: [Project!]! +} + +type Project { + id: ID! + name: String! + description: String + owner: User! +} diff --git a/tests/normalization-tests/src/commonTest/kotlin/com/example/NormalizationTest.kt b/tests/normalization-tests/src/commonTest/kotlin/com/example/NormalizationTest.kt index 3272c1a3..91ba1b02 100644 --- a/tests/normalization-tests/src/commonTest/kotlin/com/example/NormalizationTest.kt +++ b/tests/normalization-tests/src/commonTest/kotlin/com/example/NormalizationTest.kt @@ -6,6 +6,7 @@ import com.apollographql.apollo.api.json.jsonReader import com.apollographql.apollo.api.toApolloResponse import com.apollographql.cache.normalized.ApolloStore import com.apollographql.cache.normalized.FetchPolicy +import com.apollographql.cache.normalized.allRecords import com.apollographql.cache.normalized.api.CacheKey import com.apollographql.cache.normalized.api.CacheKeyGenerator import com.apollographql.cache.normalized.api.CacheKeyGeneratorContext @@ -14,13 +15,17 @@ import com.apollographql.cache.normalized.api.CacheResolver import com.apollographql.cache.normalized.api.FieldPolicyCacheResolver import com.apollographql.cache.normalized.api.ResolverContext import com.apollographql.cache.normalized.api.TypePolicyCacheKeyGenerator +import com.apollographql.cache.normalized.apolloStore import com.apollographql.cache.normalized.fetchPolicy import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.normalizedCache +import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory import com.apollographql.cache.normalized.store +import com.apollographql.cache.normalized.testing.append import com.apollographql.cache.normalized.testing.runTest import com.apollographql.mockserver.MockServer import com.apollographql.mockserver.enqueueString +import com.example.four.CreateUserMutation import com.example.one.Issue2818Query import com.example.one.Issue3672Query import com.example.one.fragment.SectionFragment @@ -35,6 +40,7 @@ import com.example.two.GetCountryQuery import com.example.two.NestedFragmentQuery import okio.Buffer import kotlin.test.Test +import kotlin.test.assertContentEquals import kotlin.test.assertEquals internal object IdBasedCacheKeyResolver : CacheResolver, CacheKeyGenerator { @@ -309,4 +315,58 @@ class NormalizationTest { apolloClient.close() mockserver.close() } + + @Test + fun mutationRoot() = runTest { + val mockserver = MockServer() + val apolloClient = ApolloClient.Builder() + .serverUrl(mockserver.url()) + .normalizedCache(SqlNormalizedCacheFactory()) + .build() + + apolloClient.apolloStore.clearAll() + mockserver.enqueueString( + // language=JSON + """ + { + "data": { + "createUser": { + "__typename": "User", + "id": "user-1", + "name": "John Doe", + "projects": [ + { + "__typename": "Project", + "id": "project-1", + "name": "Project 1", + "description": "Description 1", + "owner": { + "__typename": "User", + "id": "user-2", + "name": "Jane Doe" + } + } + ] + } + } + } + """.trimIndent() + ) + apolloClient.mutation(CreateUserMutation("John")).fetchPolicy(FetchPolicy.NetworkOnly).execute() + + apolloClient.apolloStore.accessCache { normalizedCache -> + assertContentEquals( + listOf( + CacheKey.MUTATION_ROOT, + CacheKey.MUTATION_ROOT.append("""createUser({"name":"John"})"""), + CacheKey.MUTATION_ROOT.append("""createUser({"name":"John"})""", "projects", "0"), + CacheKey.MUTATION_ROOT.append("""createUser({"name":"John"})""", "projects", "0", "owner"), + ), + normalizedCache.allRecords().keys, + ) + } + + apolloClient.close() + mockserver.close() + } } diff --git a/tests/normalized-cache/src/commonTest/kotlin/FetchPolicyTest.kt b/tests/normalized-cache/src/commonTest/kotlin/FetchPolicyTest.kt index ad4aff59..d7931b97 100644 --- a/tests/normalized-cache/src/commonTest/kotlin/FetchPolicyTest.kt +++ b/tests/normalized-cache/src/commonTest/kotlin/FetchPolicyTest.kt @@ -603,7 +603,7 @@ class FetchPolicyTest { ) } ) - store.publish(setOf(CacheKey.rootKey().fieldKey("hero"))) + store.publish(setOf(CacheKey.QUERY_ROOT.fieldKey("hero"))) /** * This time the watcher should do a network request diff --git a/tests/normalized-cache/src/commonTest/kotlin/NormalizerTest.kt b/tests/normalized-cache/src/commonTest/kotlin/NormalizerTest.kt index ba18a988..2b411063 100644 --- a/tests/normalized-cache/src/commonTest/kotlin/NormalizerTest.kt +++ b/tests/normalized-cache/src/commonTest/kotlin/NormalizerTest.kt @@ -48,7 +48,7 @@ class NormalizerTest { @Throws(Exception::class) fun testHeroName() { val records = records(HeroNameQuery(), "HeroNameResponse.json") - val record = records.get(CacheKey.rootKey()) + val record = records.get(CacheKey.QUERY_ROOT) val reference = record!!["hero"] as CacheKey? assertEquals(reference, CacheKey("hero")) val heroRecord = records.get(reference!!) @@ -79,7 +79,7 @@ class NormalizerTest { @Throws(Exception::class) fun testHeroNameWithVariable() { val records = records(EpisodeHeroNameQuery(Episode.JEDI), "EpisodeHeroNameResponse.json") - val record = records.get(CacheKey.rootKey()) + val record = records.get(CacheKey.QUERY_ROOT) val reference = record!![TEST_FIELD_KEY_JEDI] as CacheKey? assertEquals(reference, CacheKey(TEST_FIELD_KEY_JEDI)) val heroRecord = records.get(reference!!) @@ -91,7 +91,7 @@ class NormalizerTest { fun testHeroAppearsInQuery() { val records = records(HeroAppearsInQuery(), "HeroAppearsInResponse.json") - val rootRecord = records.get(CacheKey.rootKey())!! + val rootRecord = records.get(CacheKey.QUERY_ROOT)!! val heroReference = rootRecord["hero"] as CacheKey? assertEquals(heroReference, CacheKey("hero")) @@ -104,7 +104,7 @@ class NormalizerTest { @Throws(Exception::class) fun testHeroAndFriendsNamesQueryWithoutIDs() { val records = records(HeroAndFriendsNamesQuery(Episode.JEDI), "HeroAndFriendsNameResponse.json") - val record = records.get(CacheKey.rootKey()) + val record = records.get(CacheKey.QUERY_ROOT) val heroReference = record!![TEST_FIELD_KEY_JEDI] as CacheKey? assertEquals(heroReference, CacheKey(TEST_FIELD_KEY_JEDI)) val heroRecord = records.get(heroReference!!) @@ -125,7 +125,7 @@ class NormalizerTest { @Throws(Exception::class) fun testHeroAndFriendsNamesQueryWithIDs() { val records = records(HeroAndFriendsNamesWithIDsQuery(Episode.JEDI), "HeroAndFriendsNameWithIdsResponse.json") - val record = records.get(CacheKey.rootKey()) + val record = records.get(CacheKey.QUERY_ROOT) val heroReference = record!![TEST_FIELD_KEY_JEDI] as CacheKey? assertEquals(CacheKey("Character:2001"), heroReference) val heroRecord = records.get(heroReference!!) @@ -146,7 +146,7 @@ class NormalizerTest { @Throws(Exception::class) fun testHeroAndFriendsNamesWithIDForParentOnly() { val records = records(HeroAndFriendsNamesWithIDForParentOnlyQuery(Episode.JEDI), "HeroAndFriendsNameWithIdsParentOnlyResponse.json") - val record = records[CacheKey.rootKey()] + val record = records[CacheKey.QUERY_ROOT] val heroReference = record!![TEST_FIELD_KEY_JEDI] as CacheKey? assertEquals(CacheKey("Character:2001"), heroReference) val heroRecord = records.get(heroReference!!) @@ -167,7 +167,7 @@ class NormalizerTest { @Throws(Exception::class) fun testSameHeroTwiceQuery() { val records = records(SameHeroTwiceQuery(), "SameHeroTwiceResponse.json") - val record = records.get(CacheKey.rootKey()) + val record = records.get(CacheKey.QUERY_ROOT) val heroReference = record!!["hero"] as CacheKey? val hero = records.get(heroReference!!) @@ -179,7 +179,7 @@ class NormalizerTest { @Throws(Exception::class) fun testHeroTypeDependentAliasedFieldQueryDroid() { val records = records(HeroTypeDependentAliasedFieldQuery(Episode.JEDI), "HeroTypeDependentAliasedFieldResponse.json") - val record = records.get(CacheKey.rootKey()) + val record = records.get(CacheKey.QUERY_ROOT) val heroReference = record!![TEST_FIELD_KEY_JEDI] as CacheKey? val hero = records.get(heroReference!!) assertEquals(hero!!["primaryFunction"], "Astromech") @@ -190,7 +190,7 @@ class NormalizerTest { @Throws(Exception::class) fun testHeroTypeDependentAliasedFieldQueryHuman() { val records = records(HeroTypeDependentAliasedFieldQuery(Episode.EMPIRE), "HeroTypeDependentAliasedFieldResponseHuman.json") - val record = records.get(CacheKey.rootKey()) + val record = records.get(CacheKey.QUERY_ROOT) val heroReference = record!![TEST_FIELD_KEY_EMPIRE] as CacheKey? val hero = records.get(heroReference!!) assertEquals(hero!!["homePlanet"], "Tatooine") @@ -201,7 +201,7 @@ class NormalizerTest { @Throws(Exception::class) fun testHeroParentTypeDependentAliasedFieldQueryHuman() { val records = records(HeroTypeDependentAliasedFieldQuery(Episode.EMPIRE), "HeroTypeDependentAliasedFieldResponseHuman.json") - val record = records.get(CacheKey.rootKey()) + val record = records.get(CacheKey.QUERY_ROOT) val heroReference = record!![TEST_FIELD_KEY_EMPIRE] as CacheKey? val hero = records.get(heroReference!!) assertEquals(hero!!["homePlanet"], "Tatooine") diff --git a/tests/normalized-cache/src/jvmTest/kotlin/WriteToCacheAsynchronouslyTest.kt b/tests/normalized-cache/src/jvmTest/kotlin/WriteToCacheAsynchronouslyTest.kt index 56bf959b..8f6eccb4 100644 --- a/tests/normalized-cache/src/jvmTest/kotlin/WriteToCacheAsynchronouslyTest.kt +++ b/tests/normalized-cache/src/jvmTest/kotlin/WriteToCacheAsynchronouslyTest.kt @@ -82,6 +82,6 @@ class WriteToCacheAsynchronouslyTest { } companion object { - val QUERY_ROOT_KEY = CacheKey.rootKey() + val QUERY_ROOT_KEY = CacheKey.QUERY_ROOT } } From 2b5fe64a6bb28738ec7c5eceba15888679f24cc9 Mon Sep 17 00:00:00 2001 From: BoD Date: Mon, 7 Apr 2025 11:51:49 +0200 Subject: [PATCH 2/5] Improve blob storage of mutation/subscription prefixes --- .../sql/internal/RecordSerializer.kt | 35 ++++++++- tests/normalization-tests/build.gradle.kts | 5 ++ .../kotlin/com/example/NormalizationTest.kt | 60 --------------- .../com/example/SqlNormalizationTest.kt | 73 +++++++++++++++++++ 4 files changed, 109 insertions(+), 64 deletions(-) create mode 100644 tests/normalization-tests/src/concurrentTest/kotlin/com/example/SqlNormalizationTest.kt diff --git a/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/internal/RecordSerializer.kt b/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/internal/RecordSerializer.kt index 79a22f6d..178e1951 100644 --- a/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/internal/RecordSerializer.kt +++ b/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/internal/RecordSerializer.kt @@ -20,7 +20,7 @@ internal object RecordSerializer { buffer.writeMap(record.fields) buffer._writeInt(record.metadata.size) for ((k, v) in record.metadata.mapKeys { (k, _) -> knownMetadataKeys[k] ?: k }) { - buffer.writeString(k) + buffer.writeString(k.shortenCacheKey()) buffer.writeMap(v) } return buffer.readByteArray() @@ -32,7 +32,7 @@ internal object RecordSerializer { val metadataSize = buffer._readInt() val metadata = HashMap>(metadataSize).apply { repeat(metadataSize) { - val k = buffer.readString() + val k = buffer.readString().expandCacheKey() val v = buffer.readMap() put(k, v) } @@ -174,7 +174,7 @@ internal object RecordSerializer { is CacheKey -> { writeByte(CACHE_KEY) - writeString(value.key) + writeString(value.key.shortenCacheKey()) } is List<*> -> { @@ -242,7 +242,7 @@ internal object RecordSerializer { BOOLEAN_TRUE -> true BOOLEAN_FALSE -> false CACHE_KEY -> { - CacheKey(readString()) + CacheKey(readString().expandCacheKey()) } LIST -> { @@ -321,4 +321,31 @@ internal object RecordSerializer { ApolloCacheHeaders.EXPIRATION_DATE to "1", ) private val knownMetadataKeysInverted = knownMetadataKeys.entries.associate { (k, v) -> v to k } + + private val mutationPrefixLong = CacheKey.MUTATION_ROOT.key + "." + private val subscriptionPrefixLong = CacheKey.SUBSCRIPTION_ROOT.key + "." + + // Use non printable characters to reduce likelihood of collisions with legitimate cache keys + private const val mutationPrefixShort = "\u0001" + private const val subscriptionPrefixShort = "\u0002" + + private fun String.shortenCacheKey(): String { + return if (startsWith(mutationPrefixLong)) { + replaceFirst(mutationPrefixLong, mutationPrefixShort) + } else if (startsWith(subscriptionPrefixLong)) { + replaceFirst(subscriptionPrefixLong, subscriptionPrefixShort) + } else { + this + } + } + + private fun String.expandCacheKey(): String { + return if (startsWith(mutationPrefixShort)) { + replaceFirst(mutationPrefixShort, mutationPrefixLong) + } else if (startsWith(subscriptionPrefixShort)) { + replaceFirst(subscriptionPrefixShort, subscriptionPrefixLong) + } else { + this + } + } } diff --git a/tests/normalization-tests/build.gradle.kts b/tests/normalization-tests/build.gradle.kts index 45733081..be84761d 100644 --- a/tests/normalization-tests/build.gradle.kts +++ b/tests/normalization-tests/build.gradle.kts @@ -16,6 +16,11 @@ kotlin { dependencies { implementation(libs.apollo.runtime) implementation("com.apollographql.cache:normalized-cache-incubating") + } + } + + getByName("concurrentMain") { + dependencies { implementation("com.apollographql.cache:normalized-cache-sqlite-incubating") } } diff --git a/tests/normalization-tests/src/commonTest/kotlin/com/example/NormalizationTest.kt b/tests/normalization-tests/src/commonTest/kotlin/com/example/NormalizationTest.kt index 91ba1b02..3272c1a3 100644 --- a/tests/normalization-tests/src/commonTest/kotlin/com/example/NormalizationTest.kt +++ b/tests/normalization-tests/src/commonTest/kotlin/com/example/NormalizationTest.kt @@ -6,7 +6,6 @@ import com.apollographql.apollo.api.json.jsonReader import com.apollographql.apollo.api.toApolloResponse import com.apollographql.cache.normalized.ApolloStore import com.apollographql.cache.normalized.FetchPolicy -import com.apollographql.cache.normalized.allRecords import com.apollographql.cache.normalized.api.CacheKey import com.apollographql.cache.normalized.api.CacheKeyGenerator import com.apollographql.cache.normalized.api.CacheKeyGeneratorContext @@ -15,17 +14,13 @@ import com.apollographql.cache.normalized.api.CacheResolver import com.apollographql.cache.normalized.api.FieldPolicyCacheResolver import com.apollographql.cache.normalized.api.ResolverContext import com.apollographql.cache.normalized.api.TypePolicyCacheKeyGenerator -import com.apollographql.cache.normalized.apolloStore import com.apollographql.cache.normalized.fetchPolicy import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.normalizedCache -import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory import com.apollographql.cache.normalized.store -import com.apollographql.cache.normalized.testing.append import com.apollographql.cache.normalized.testing.runTest import com.apollographql.mockserver.MockServer import com.apollographql.mockserver.enqueueString -import com.example.four.CreateUserMutation import com.example.one.Issue2818Query import com.example.one.Issue3672Query import com.example.one.fragment.SectionFragment @@ -40,7 +35,6 @@ import com.example.two.GetCountryQuery import com.example.two.NestedFragmentQuery import okio.Buffer import kotlin.test.Test -import kotlin.test.assertContentEquals import kotlin.test.assertEquals internal object IdBasedCacheKeyResolver : CacheResolver, CacheKeyGenerator { @@ -315,58 +309,4 @@ class NormalizationTest { apolloClient.close() mockserver.close() } - - @Test - fun mutationRoot() = runTest { - val mockserver = MockServer() - val apolloClient = ApolloClient.Builder() - .serverUrl(mockserver.url()) - .normalizedCache(SqlNormalizedCacheFactory()) - .build() - - apolloClient.apolloStore.clearAll() - mockserver.enqueueString( - // language=JSON - """ - { - "data": { - "createUser": { - "__typename": "User", - "id": "user-1", - "name": "John Doe", - "projects": [ - { - "__typename": "Project", - "id": "project-1", - "name": "Project 1", - "description": "Description 1", - "owner": { - "__typename": "User", - "id": "user-2", - "name": "Jane Doe" - } - } - ] - } - } - } - """.trimIndent() - ) - apolloClient.mutation(CreateUserMutation("John")).fetchPolicy(FetchPolicy.NetworkOnly).execute() - - apolloClient.apolloStore.accessCache { normalizedCache -> - assertContentEquals( - listOf( - CacheKey.MUTATION_ROOT, - CacheKey.MUTATION_ROOT.append("""createUser({"name":"John"})"""), - CacheKey.MUTATION_ROOT.append("""createUser({"name":"John"})""", "projects", "0"), - CacheKey.MUTATION_ROOT.append("""createUser({"name":"John"})""", "projects", "0", "owner"), - ), - normalizedCache.allRecords().keys, - ) - } - - apolloClient.close() - mockserver.close() - } } diff --git a/tests/normalization-tests/src/concurrentTest/kotlin/com/example/SqlNormalizationTest.kt b/tests/normalization-tests/src/concurrentTest/kotlin/com/example/SqlNormalizationTest.kt new file mode 100644 index 00000000..ac461d79 --- /dev/null +++ b/tests/normalization-tests/src/concurrentTest/kotlin/com/example/SqlNormalizationTest.kt @@ -0,0 +1,73 @@ +package com.example + +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.testing.internal.runTest +import com.apollographql.cache.normalized.FetchPolicy +import com.apollographql.cache.normalized.allRecords +import com.apollographql.cache.normalized.api.CacheKey +import com.apollographql.cache.normalized.apolloStore +import com.apollographql.cache.normalized.fetchPolicy +import com.apollographql.cache.normalized.normalizedCache +import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory +import com.apollographql.cache.normalized.testing.append +import com.apollographql.mockserver.MockServer +import com.apollographql.mockserver.enqueueString +import com.example.four.CreateUserMutation +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class SqlNormalizationTest { + @Test + fun mutationRoot() = runTest { + val mockserver = MockServer() + val apolloClient = ApolloClient.Builder() + .serverUrl(mockserver.url()) + .normalizedCache(SqlNormalizedCacheFactory()) + .build() + + apolloClient.apolloStore.clearAll() + mockserver.enqueueString( + // language=JSON + """ + { + "data": { + "createUser": { + "__typename": "User", + "id": "user-1", + "name": "John Doe", + "projects": [ + { + "__typename": "Project", + "id": "project-1", + "name": "Project 1", + "description": "Description 1", + "owner": { + "__typename": "User", + "id": "user-2", + "name": "Jane Doe" + } + } + ] + } + } + } + """.trimIndent() + ) + apolloClient.mutation(CreateUserMutation("John")).fetchPolicy(FetchPolicy.NetworkOnly).execute() + + apolloClient.apolloStore.accessCache { normalizedCache -> + assertContentEquals( + listOf( + CacheKey.MUTATION_ROOT, + CacheKey.MUTATION_ROOT.append("""createUser({"name":"John"})"""), + CacheKey.MUTATION_ROOT.append("""createUser({"name":"John"})""", "projects", "0"), + CacheKey.MUTATION_ROOT.append("""createUser({"name":"John"})""", "projects", "0", "owner"), + ), + normalizedCache.allRecords().keys, + ) + } + + apolloClient.close() + mockserver.close() + } +} From cd3bbb9429b9ebaaaa03cfda133d0106df722443 Mon Sep 17 00:00:00 2001 From: BoD Date: Mon, 7 Apr 2025 14:48:20 +0200 Subject: [PATCH 3/5] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb0f01c8..34c9c188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Next version (unreleased) -PUT_CHANGELOG_HERE +- Records are now rooted per operation type (QUERY_ROOT, MUTATION_ROOT, SUBSCRIPTION_ROOT) (#109) # Version 0.0.8 _2025-03-28_ From 150971381f6aee49d2cec86eb5440685597fcb4a Mon Sep 17 00:00:00 2001 From: BoD Date: Wed, 9 Apr 2025 10:28:18 +0200 Subject: [PATCH 4/5] Mention the change in the migration guide --- Writerside/topics/migration-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Writerside/topics/migration-guide.md b/Writerside/topics/migration-guide.md index d403ff9b..4a2463ba 100644 --- a/Writerside/topics/migration-guide.md +++ b/Writerside/topics/migration-guide.md @@ -105,7 +105,7 @@ store.writeOperation(operation, data).also { store.publish(it) } ### Other changes - `readFragment()` now returns a `ReadResult` (it previously returned a ``). This allows for surfacing metadata associated to the returned data, e.g. staleness. - +- Records are now rooted per operation type (`QUERY_ROOT`, `MUTATION_ROOT`, `SUBSCRIPTION_ROOT`), when previously these were all at the same level, which could cause conflicts. ## CacheResolver, CacheKeyResolver From eaf33a7e9d16f702593b51a5bb2c0356263c9cad Mon Sep 17 00:00:00 2001 From: BoD Date: Wed, 9 Apr 2025 11:12:07 +0200 Subject: [PATCH 5/5] Fix import --- .../concurrentTest/kotlin/com/example/SqlNormalizationTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/normalization-tests/src/concurrentTest/kotlin/com/example/SqlNormalizationTest.kt b/tests/normalization-tests/src/concurrentTest/kotlin/com/example/SqlNormalizationTest.kt index ac461d79..72a5d1b6 100644 --- a/tests/normalization-tests/src/concurrentTest/kotlin/com/example/SqlNormalizationTest.kt +++ b/tests/normalization-tests/src/concurrentTest/kotlin/com/example/SqlNormalizationTest.kt @@ -1,7 +1,6 @@ package com.example import com.apollographql.apollo.ApolloClient -import com.apollographql.apollo.testing.internal.runTest import com.apollographql.cache.normalized.FetchPolicy import com.apollographql.cache.normalized.allRecords import com.apollographql.cache.normalized.api.CacheKey @@ -10,6 +9,7 @@ import com.apollographql.cache.normalized.fetchPolicy import com.apollographql.cache.normalized.normalizedCache import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory import com.apollographql.cache.normalized.testing.append +import com.apollographql.cache.normalized.testing.runTest import com.apollographql.mockserver.MockServer import com.apollographql.mockserver.enqueueString import com.example.four.CreateUserMutation