From 5ab62a3b21d0249e84c974bc1b19fdf838ca2939 Mon Sep 17 00:00:00 2001 From: BoD Date: Mon, 15 Dec 2025 14:45:09 +0100 Subject: [PATCH] Add ReadOnlyNormalizedCache.size and ReadOnlyNormalizedCache.nextCache --- CHANGELOG.md | 2 + .../api/android/normalized-cache-sqlite.api | 2 + .../api/jvm/normalized-cache-sqlite.api | 2 + .../api/normalized-cache-sqlite.klib.api | 1 + .../normalized/sql/SqlNormalizedCache.kt | 15 ++++++- normalized-cache/api/normalized-cache.api | 9 +++++ .../api/normalized-cache.klib.api | 8 ++++ .../normalized/api/ReadOnlyNormalizedCache.kt | 17 +++++++- .../internal/OptimisticNormalizedCache.kt | 26 +++++++----- .../cache/normalized/memory/MemoryCache.kt | 8 ++-- .../cache/normalized/MemoryCacheTest.kt | 4 +- .../src/concurrentTest/kotlin/ChainedTest.kt | 40 +++++++++++++++++++ .../kotlin/ConnectionPaginationTest.kt | 2 + ...onProgrammaticConnectionsPaginationTest.kt | 2 + ...nProgrammaticTypePoliciesPaginationTest.kt | 2 + .../ConnectionWithNodesPaginationTest.kt | 2 + .../ConnectionWithUnionPaginationTest.kt | 2 + .../kotlin/CursorBasedPaginationTest.kt | 2 + 18 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 tests/normalized-cache/src/concurrentTest/kotlin/ChainedTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index e7087073c..6d7ab0c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ PUT_CHANGELOG_HERE +- Add `ReadOnlyNormalizedCache.nextCache` and `ReadOnlyNormalizedCache.size()` (#280) + # v1.0.0-beta.1 _2025-12-09_ diff --git a/normalized-cache-sqlite/api/android/normalized-cache-sqlite.api b/normalized-cache-sqlite/api/android/normalized-cache-sqlite.api index 442d65e66..f7569a6a1 100644 --- a/normalized-cache-sqlite/api/android/normalized-cache-sqlite.api +++ b/normalized-cache-sqlite/api/android/normalized-cache-sqlite.api @@ -13,12 +13,14 @@ public final class com/apollographql/cache/normalized/sql/SqlNormalizedCache : c public fun clearAll (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun dump (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getNextCache ()Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache; public fun loadRecord-7OwBRqc (Ljava/lang/String;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun loadRecords (Ljava/util/Collection;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun merge (Lcom/apollographql/cache/normalized/api/Record;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lcom/apollographql/cache/normalized/api/RecordMerger;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun merge (Ljava/util/Collection;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lcom/apollographql/cache/normalized/api/RecordMerger;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remove (Ljava/util/Collection;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remove-7OwBRqc (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun size (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun sizeOfRecord (Lcom/apollographql/cache/normalized/api/Record;)I public fun trim (JFLkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/normalized-cache-sqlite/api/jvm/normalized-cache-sqlite.api b/normalized-cache-sqlite/api/jvm/normalized-cache-sqlite.api index 49a83803b..afa1cf007 100644 --- a/normalized-cache-sqlite/api/jvm/normalized-cache-sqlite.api +++ b/normalized-cache-sqlite/api/jvm/normalized-cache-sqlite.api @@ -2,12 +2,14 @@ public final class com/apollographql/cache/normalized/sql/SqlNormalizedCache : c public fun clearAll (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun dump (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getNextCache ()Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache; public fun loadRecord-7OwBRqc (Ljava/lang/String;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun loadRecords (Ljava/util/Collection;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun merge (Lcom/apollographql/cache/normalized/api/Record;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lcom/apollographql/cache/normalized/api/RecordMerger;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun merge (Ljava/util/Collection;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lcom/apollographql/cache/normalized/api/RecordMerger;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remove (Ljava/util/Collection;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remove-7OwBRqc (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun size (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun sizeOfRecord (Lcom/apollographql/cache/normalized/api/Record;)I public fun trim (JFLkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/normalized-cache-sqlite/api/normalized-cache-sqlite.klib.api b/normalized-cache-sqlite/api/normalized-cache-sqlite.klib.api index 99523c707..07f08edc6 100644 --- a/normalized-cache-sqlite/api/normalized-cache-sqlite.klib.api +++ b/normalized-cache-sqlite/api/normalized-cache-sqlite.klib.api @@ -97,6 +97,7 @@ final class com.apollographql.cache.normalized.sql/SqlNormalizedCache : com.apol final suspend fun merge(kotlin.collections/Collection, com.apollographql.cache.normalized.api/CacheHeaders, com.apollographql.cache.normalized.api/RecordMerger): kotlin.collections/Set // com.apollographql.cache.normalized.sql/SqlNormalizedCache.merge|merge(kotlin.collections.Collection;com.apollographql.cache.normalized.api.CacheHeaders;com.apollographql.cache.normalized.api.RecordMerger){}[0] final suspend fun remove(com.apollographql.cache.normalized.api/CacheKey, kotlin/Boolean): kotlin/Boolean // com.apollographql.cache.normalized.sql/SqlNormalizedCache.remove|remove(com.apollographql.cache.normalized.api.CacheKey;kotlin.Boolean){}[0] final suspend fun remove(kotlin.collections/Collection, kotlin/Boolean): kotlin/Int // com.apollographql.cache.normalized.sql/SqlNormalizedCache.remove|remove(kotlin.collections.Collection;kotlin.Boolean){}[0] + final suspend fun size(): kotlin/Long // com.apollographql.cache.normalized.sql/SqlNormalizedCache.size|size(){}[0] final suspend fun trim(kotlin/Long, kotlin/Float): kotlin/Long // com.apollographql.cache.normalized.sql/SqlNormalizedCache.trim|trim(kotlin.Long;kotlin.Float){}[0] } diff --git a/normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt b/normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt index 942cb2b7a..dd168184b 100644 --- a/normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt +++ b/normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt @@ -93,6 +93,17 @@ class SqlNormalizedCache internal constructor( return keySize + RecordSerializer.serialize(record).size } + override suspend fun size(): Long { + return try { + recordDatabase.init() + recordDatabase.databaseSize() + } catch (e: Exception) { + // Unable to get the size of the database, it is possibly corrupted + apolloExceptionHandler(Exception("Unable to get the size of the database", e)) + -1 + } + } + private suspend fun getReferencedKeysRecursively( keys: Collection, visited: MutableSet = mutableSetOf(), @@ -174,8 +185,8 @@ class SqlNormalizedCache internal constructor( override suspend fun trim(maxSizeBytes: Long, trimFactor: Float): Long { try { - recordDatabase.init() - val size = recordDatabase.databaseSize() + val size = size() + if (size == -1L) return -1 return if (size >= maxSizeBytes) { val count = recordDatabase.count() recordDatabase.trimByUpdatedDate((count * trimFactor).toLong()) diff --git a/normalized-cache/api/normalized-cache.api b/normalized-cache/api/normalized-cache.api index 587b6fd30..9dc6eb1fa 100644 --- a/normalized-cache/api/normalized-cache.api +++ b/normalized-cache/api/normalized-cache.api @@ -618,6 +618,8 @@ public final class com/apollographql/cache/normalized/api/NormalizedCache$Compan public final class com/apollographql/cache/normalized/api/NormalizedCache$DefaultImpls { public static fun close (Lcom/apollographql/cache/normalized/api/NormalizedCache;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun getNextCache (Lcom/apollographql/cache/normalized/api/NormalizedCache;)Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache; + public static fun size (Lcom/apollographql/cache/normalized/api/NormalizedCache;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sizeOfRecord (Lcom/apollographql/cache/normalized/api/NormalizedCache;Lcom/apollographql/cache/normalized/api/Record;)I public static fun trim (Lcom/apollographql/cache/normalized/api/NormalizedCache;JFLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun trim$default (Lcom/apollographql/cache/normalized/api/NormalizedCache;JFLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; @@ -631,13 +633,17 @@ public abstract class com/apollographql/cache/normalized/api/NormalizedCacheFact public abstract interface class com/apollographql/cache/normalized/api/ReadOnlyNormalizedCache { public abstract fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun dump (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getNextCache ()Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache; public abstract fun loadRecord-7OwBRqc (Ljava/lang/String;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun loadRecords (Ljava/util/Collection;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun size (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun sizeOfRecord (Lcom/apollographql/cache/normalized/api/Record;)I } public final class com/apollographql/cache/normalized/api/ReadOnlyNormalizedCache$DefaultImpls { public static fun close (Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun getNextCache (Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache;)Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache; + public static fun size (Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sizeOfRecord (Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache;Lcom/apollographql/cache/normalized/api/Record;)I } @@ -767,12 +773,15 @@ public final class com/apollographql/cache/normalized/memory/MemoryCache : com/a public fun clearAll (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun dump (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getNextCache ()Lcom/apollographql/cache/normalized/api/NormalizedCache; + public synthetic fun getNextCache ()Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache; public fun loadRecord-7OwBRqc (Ljava/lang/String;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun loadRecords (Ljava/util/Collection;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun merge (Lcom/apollographql/cache/normalized/api/Record;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lcom/apollographql/cache/normalized/api/RecordMerger;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun merge (Ljava/util/Collection;Lcom/apollographql/cache/normalized/api/CacheHeaders;Lcom/apollographql/cache/normalized/api/RecordMerger;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remove (Ljava/util/Collection;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remove-7OwBRqc (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun size (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun sizeOfRecord (Lcom/apollographql/cache/normalized/api/Record;)I public fun trim (JFLkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/normalized-cache/api/normalized-cache.klib.api b/normalized-cache/api/normalized-cache.klib.api index 070f1658c..d81a6e1ab 100644 --- a/normalized-cache/api/normalized-cache.klib.api +++ b/normalized-cache/api/normalized-cache.klib.api @@ -71,11 +71,15 @@ abstract interface com.apollographql.cache.normalized.api/NormalizedCache : com. } abstract interface com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache { // com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache|null[0] + open val nextCache // com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache.nextCache|{}nextCache[0] + open fun (): com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache? // com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache.nextCache.|(){}[0] + abstract suspend fun dump(): kotlin.collections/Map, kotlin.collections/Map> // com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache.dump|dump(){}[0] abstract suspend fun loadRecord(com.apollographql.cache.normalized.api/CacheKey, com.apollographql.cache.normalized.api/CacheHeaders): com.apollographql.cache.normalized.api/Record? // com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache.loadRecord|loadRecord(com.apollographql.cache.normalized.api.CacheKey;com.apollographql.cache.normalized.api.CacheHeaders){}[0] abstract suspend fun loadRecords(kotlin.collections/Collection, com.apollographql.cache.normalized.api/CacheHeaders): kotlin.collections/Collection // com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache.loadRecords|loadRecords(kotlin.collections.Collection;com.apollographql.cache.normalized.api.CacheHeaders){}[0] open fun sizeOfRecord(com.apollographql.cache.normalized.api/Record): kotlin/Int // com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache.sizeOfRecord|sizeOfRecord(com.apollographql.cache.normalized.api.Record){}[0] open suspend fun close() // com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache.close|close(){}[0] + open suspend fun size(): kotlin/Long // com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache.size|size(){}[0] } abstract interface com.apollographql.cache.normalized.api/RecordMerger { // com.apollographql.cache.normalized.api/RecordMerger|null[0] @@ -402,6 +406,9 @@ final class com.apollographql.cache.normalized.api/TypePolicyCacheKeyGenerator : final class com.apollographql.cache.normalized.memory/MemoryCache : com.apollographql.cache.normalized.api/NormalizedCache { // com.apollographql.cache.normalized.memory/MemoryCache|null[0] constructor (com.apollographql.cache.normalized.api/NormalizedCache? = ..., kotlin/Int = ..., kotlin/Long = ...) // com.apollographql.cache.normalized.memory/MemoryCache.|(com.apollographql.cache.normalized.api.NormalizedCache?;kotlin.Int;kotlin.Long){}[0] + final val nextCache // com.apollographql.cache.normalized.memory/MemoryCache.nextCache|{}nextCache[0] + final fun (): com.apollographql.cache.normalized.api/NormalizedCache? // com.apollographql.cache.normalized.memory/MemoryCache.nextCache.|(){}[0] + final fun sizeOfRecord(com.apollographql.cache.normalized.api/Record): kotlin/Int // com.apollographql.cache.normalized.memory/MemoryCache.sizeOfRecord|sizeOfRecord(com.apollographql.cache.normalized.api.Record){}[0] final suspend fun clearAll() // com.apollographql.cache.normalized.memory/MemoryCache.clearAll|clearAll(){}[0] final suspend fun dump(): kotlin.collections/Map, kotlin.collections/Map> // com.apollographql.cache.normalized.memory/MemoryCache.dump|dump(){}[0] @@ -411,6 +418,7 @@ final class com.apollographql.cache.normalized.memory/MemoryCache : com.apollogr final suspend fun merge(kotlin.collections/Collection, com.apollographql.cache.normalized.api/CacheHeaders, com.apollographql.cache.normalized.api/RecordMerger): kotlin.collections/Set // com.apollographql.cache.normalized.memory/MemoryCache.merge|merge(kotlin.collections.Collection;com.apollographql.cache.normalized.api.CacheHeaders;com.apollographql.cache.normalized.api.RecordMerger){}[0] final suspend fun remove(com.apollographql.cache.normalized.api/CacheKey, kotlin/Boolean): kotlin/Boolean // com.apollographql.cache.normalized.memory/MemoryCache.remove|remove(com.apollographql.cache.normalized.api.CacheKey;kotlin.Boolean){}[0] final suspend fun remove(kotlin.collections/Collection, kotlin/Boolean): kotlin/Int // com.apollographql.cache.normalized.memory/MemoryCache.remove|remove(kotlin.collections.Collection;kotlin.Boolean){}[0] + final suspend fun size(): kotlin/Long // com.apollographql.cache.normalized.memory/MemoryCache.size|size(){}[0] final suspend fun trim(kotlin/Long, kotlin/Float): kotlin/Long // com.apollographql.cache.normalized.memory/MemoryCache.trim|trim(kotlin.Long;kotlin.Float){}[0] } diff --git a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ReadOnlyNormalizedCache.kt b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ReadOnlyNormalizedCache.kt index 6520492bb..853777a1f 100644 --- a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ReadOnlyNormalizedCache.kt +++ b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ReadOnlyNormalizedCache.kt @@ -25,9 +25,24 @@ interface ReadOnlyNormalizedCache { /** * Returns the size in bytes of a [Record]. - * This is an optional operation that can be implemented by the caches for debug purposes, otherwise it defaults to `-1`, meaning unknown size. + * This is an optional operation that can be implemented by caches for debug purposes, otherwise it defaults to `-1`, meaning unknown size. */ fun sizeOfRecord(record: Record): Int = -1 + /** + * Returns the total size in bytes of this cache. + * This is an optional operation that can be implemented by caches for debug or observability purposes, otherwise it defaults to `-1`, meaning unknown size. + * + * Note: in case of a chained cache, this size does not include the size of the next cache in the chain. See [nextCache]. + */ + suspend fun size(): Long = -1L + + /** + * Some cache implementations (e.g. [com.apollographql.cache.normalized.internal.OptimisticNormalizedCache] and [com.apollographql.cache.normalized.memory.MemoryCache]) support chaining caches. + * For those, this returns the next cache in the chain or `null` if there is none. Implementations that don't support chaining return `null`. + */ + val nextCache: ReadOnlyNormalizedCache? + get() = null + suspend fun close() {} } diff --git a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/OptimisticNormalizedCache.kt b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/OptimisticNormalizedCache.kt index cca793439..fe3e18883 100644 --- a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/OptimisticNormalizedCache.kt +++ b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/OptimisticNormalizedCache.kt @@ -10,31 +10,33 @@ import com.benasher44.uuid.Uuid import kotlin.math.max import kotlin.reflect.KClass -internal class OptimisticNormalizedCache(private val wrapped: NormalizedCache) : NormalizedCache { +internal class OptimisticNormalizedCache( + override val nextCache: NormalizedCache +) : NormalizedCache { private val recordJournals = ConcurrentMap() override suspend fun loadRecord(key: CacheKey, cacheHeaders: CacheHeaders): Record? { - val nonOptimisticRecord = wrapped.loadRecord(key, cacheHeaders) + val nonOptimisticRecord = nextCache.loadRecord(key, cacheHeaders) return nonOptimisticRecord.mergeJournalRecord(key) } override suspend fun loadRecords(keys: Collection, cacheHeaders: CacheHeaders): Collection { - val nonOptimisticRecords = wrapped.loadRecords(keys, cacheHeaders).associateBy { it.key } + val nonOptimisticRecords = nextCache.loadRecords(keys, cacheHeaders).associateBy { it.key } return keys.mapNotNull { key -> nonOptimisticRecords[key].mergeJournalRecord(key) } } override suspend fun merge(record: Record, cacheHeaders: CacheHeaders, recordMerger: RecordMerger): Set { - return wrapped.merge(record, cacheHeaders, recordMerger) + return nextCache.merge(record, cacheHeaders, recordMerger) } override suspend fun merge(records: Collection, cacheHeaders: CacheHeaders, recordMerger: RecordMerger): Set { - return wrapped.merge(records, cacheHeaders, recordMerger) + return nextCache.merge(records, cacheHeaders, recordMerger) } override suspend fun clearAll() { - wrapped.clearAll() + nextCache.clearAll() recordJournals.clear() } @@ -43,11 +45,11 @@ internal class OptimisticNormalizedCache(private val wrapped: NormalizedCache) : } override suspend fun remove(cacheKeys: Collection, cascade: Boolean): Int { - return wrapped.remove(cacheKeys, cascade) + internalRemove(cacheKeys, cascade) + return nextCache.remove(cacheKeys, cascade) + internalRemove(cacheKeys, cascade) } override suspend fun trim(maxSizeBytes: Long, trimFactor: Float): Long { - return wrapped.trim(maxSizeBytes, trimFactor) + return nextCache.trim(maxSizeBytes, trimFactor) } private fun internalRemove(cacheKeys: Collection, cascade: Boolean): Int { @@ -101,11 +103,15 @@ internal class OptimisticNormalizedCache(private val wrapped: NormalizedCache) : } override suspend fun dump(): Map, Map> { - return mapOf(this::class to recordJournals.mapValues { (_, journal) -> journal.current }) + wrapped.dump() + return mapOf(this::class to recordJournals.mapValues { (_, journal) -> journal.current }) + nextCache.dump() } override fun sizeOfRecord(record: Record): Int { - return wrapped.sizeOfRecord(record) + return nextCache.sizeOfRecord(record) + } + + override suspend fun size(): Long { + return nextCache.size() } private fun Record?.mergeJournalRecord(key: CacheKey): Record? { diff --git a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt index ccff15564..46452c798 100644 --- a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt +++ b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt @@ -26,7 +26,7 @@ import kotlin.reflect.KClass * (there is no any sort of GC that runs in the background). */ class MemoryCache( - private val nextCache: NormalizedCache? = null, + override val nextCache: NormalizedCache? = null, private val maxSizeBytes: Int = Int.MAX_VALUE, private val expireAfterMillis: Long = -1, ) : NormalizedCache { @@ -40,8 +40,6 @@ class MemoryCache( key.key.length + sizeOfRecord(record) } - internal suspend fun getSize(): Int = withLock { lruCache.weight() } - override suspend fun loadRecord(key: CacheKey, cacheHeaders: CacheHeaders): Record? = withLock { val record = lruCache[key] record ?: nextCache?.loadRecord(key, cacheHeaders)?.also { nextCachedRecord -> @@ -147,6 +145,10 @@ class MemoryCache( return RecordWeigher.calculateBytes(record) } + override suspend fun size(): Long { + return withLock { lruCache.weight() }.toLong() + } + internal fun clearCurrentCache() { lruCache.clear() } diff --git a/normalized-cache/src/commonTest/kotlin/com/apollographql/cache/normalized/MemoryCacheTest.kt b/normalized-cache/src/commonTest/kotlin/com/apollographql/cache/normalized/MemoryCacheTest.kt index 51b039c72..72d1d7f96 100644 --- a/normalized-cache/src/commonTest/kotlin/com/apollographql/cache/normalized/MemoryCacheTest.kt +++ b/normalized-cache/src/commonTest/kotlin/com/apollographql/cache/normalized/MemoryCacheTest.kt @@ -171,8 +171,8 @@ class MemoryCacheTest { primaryCache.clearAll() - assertEquals(0, primaryCache.getSize()) - assertEquals(0, secondaryCache.getSize()) + assertEquals(0, primaryCache.size()) + assertEquals(0, secondaryCache.size()) } @Test diff --git a/tests/normalized-cache/src/concurrentTest/kotlin/ChainedTest.kt b/tests/normalized-cache/src/concurrentTest/kotlin/ChainedTest.kt new file mode 100644 index 000000000..e067b7a41 --- /dev/null +++ b/tests/normalized-cache/src/concurrentTest/kotlin/ChainedTest.kt @@ -0,0 +1,40 @@ +package test + +import com.apollographql.cache.normalized.CacheManager +import com.apollographql.cache.normalized.api.CacheHeaders +import com.apollographql.cache.normalized.api.CacheKey +import com.apollographql.cache.normalized.api.DefaultCacheKeyGenerator +import com.apollographql.cache.normalized.api.DefaultCacheResolver +import com.apollographql.cache.normalized.api.DefaultRecordMerger +import com.apollographql.cache.normalized.api.ReadOnlyNormalizedCache +import com.apollographql.cache.normalized.api.Record +import com.apollographql.cache.normalized.memory.MemoryCacheFactory +import com.apollographql.cache.normalized.testing.SqlNormalizedCacheFactory +import com.apollographql.cache.normalized.testing.runTest +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class ChainedTest { + @Test + fun sizes() = runTest { + val cacheManager = CacheManager( + normalizedCacheFactory = MemoryCacheFactory().chain(SqlNormalizedCacheFactory()), + cacheKeyGenerator = DefaultCacheKeyGenerator, + cacheResolver = DefaultCacheResolver, + enableOptimisticUpdates = true, + ) + + cacheManager.accessCache { cache -> + cache.merge(Record(CacheKey("a"), fields = mapOf("name" to "Alice")), CacheHeaders.NONE, DefaultRecordMerger) + } + val sizes = mutableListOf() + cacheManager.accessCache { cache -> + var c: ReadOnlyNormalizedCache? = cache + while (c != null) { + sizes.add(c.size()) + c = c.nextCache + } + } + assertContentEquals(listOf(43, 43, 8192), sizes) + } +} diff --git a/tests/pagination/src/commonTest/kotlin/ConnectionPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/ConnectionPaginationTest.kt index 0363d54cc..8c0ce339f 100644 --- a/tests/pagination/src/commonTest/kotlin/ConnectionPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/ConnectionPaginationTest.kt @@ -2,6 +2,7 @@ package pagination import com.apollographql.apollo.api.Error import com.apollographql.apollo.api.Optional +import com.apollographql.apollo.exception.apolloExceptionHandler import com.apollographql.cache.normalized.CacheManager import com.apollographql.cache.normalized.api.ConnectionFieldKeyGenerator import com.apollographql.cache.normalized.api.ConnectionMetadataGenerator @@ -305,6 +306,7 @@ class ConnectionPaginationTest { assertChainedCachesAreEqual(cacheManager) // Non-contiguous page (should be ignored) + apolloExceptionHandler = {} val query5 = UsersQuery(first = Optional.Present(2), after = Optional.Present("xx50")) val data5 = UsersQuery.Data { users = buildUserConnection { diff --git a/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticConnectionsPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticConnectionsPaginationTest.kt index badd4c1ab..cedd82d82 100644 --- a/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticConnectionsPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticConnectionsPaginationTest.kt @@ -1,6 +1,7 @@ package pagination import com.apollographql.apollo.api.Optional +import com.apollographql.apollo.exception.apolloExceptionHandler import com.apollographql.cache.normalized.CacheManager import com.apollographql.cache.normalized.api.ConnectionEmbeddedFieldsProvider import com.apollographql.cache.normalized.api.ConnectionFieldKeyGenerator @@ -306,6 +307,7 @@ class ConnectionProgrammaticConnectionsPaginationTest { assertChainedCachesAreEqual(cacheManager) // Non-contiguous page (should be ignored) + apolloExceptionHandler = {} val query5 = UsersQuery(first = Optional.Present(2), after = Optional.Present("xx50")) val data5 = UsersQuery.Data { users = buildUserConnection { diff --git a/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticTypePoliciesPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticTypePoliciesPaginationTest.kt index d409370f9..0432185d3 100644 --- a/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticTypePoliciesPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticTypePoliciesPaginationTest.kt @@ -1,6 +1,7 @@ package pagination import com.apollographql.apollo.api.Optional +import com.apollographql.apollo.exception.apolloExceptionHandler import com.apollographql.cache.normalized.CacheManager import com.apollographql.cache.normalized.api.ConnectionEmbeddedFieldsProvider import com.apollographql.cache.normalized.api.ConnectionFieldKeyGenerator @@ -304,6 +305,7 @@ class ConnectionProgrammaticTypePoliciesPaginationTest { assertChainedCachesAreEqual(cacheManager) // Non-contiguous page (should be ignored) + apolloExceptionHandler = {} val query5 = UsersQuery(first = Optional.Present(2), after = Optional.Present("xx50")) val data5 = UsersQuery.Data { users = buildUserConnection { diff --git a/tests/pagination/src/commonTest/kotlin/ConnectionWithNodesPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/ConnectionWithNodesPaginationTest.kt index 98b268c48..f3f996035 100644 --- a/tests/pagination/src/commonTest/kotlin/ConnectionWithNodesPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/ConnectionWithNodesPaginationTest.kt @@ -1,6 +1,7 @@ package pagination import com.apollographql.apollo.api.Optional +import com.apollographql.apollo.exception.apolloExceptionHandler import com.apollographql.cache.normalized.CacheManager import com.apollographql.cache.normalized.api.ConnectionFieldKeyGenerator import com.apollographql.cache.normalized.api.ConnectionMetadataGenerator @@ -225,6 +226,7 @@ class ConnectionWithNodesPaginationTest { assertChainedCachesAreEqual(cacheManager) // Non-contiguous page (should be ignored) + apolloExceptionHandler = {} val query5 = UsersQuery(first = Optional.Present(2), after = Optional.Present("xx50")) val data5 = UsersQuery.Data { users = buildUserConnection { diff --git a/tests/pagination/src/commonTest/kotlin/ConnectionWithUnionPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/ConnectionWithUnionPaginationTest.kt index 3b523834b..196af8606 100644 --- a/tests/pagination/src/commonTest/kotlin/ConnectionWithUnionPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/ConnectionWithUnionPaginationTest.kt @@ -2,6 +2,7 @@ package pagination import com.apollographql.apollo.api.Error import com.apollographql.apollo.api.Optional +import com.apollographql.apollo.exception.apolloExceptionHandler import com.apollographql.cache.normalized.CacheManager import com.apollographql.cache.normalized.api.ConnectionFieldKeyGenerator import com.apollographql.cache.normalized.api.ConnectionMetadataGenerator @@ -324,6 +325,7 @@ class ConnectionWithUnionPaginationTest { assertChainedCachesAreEqual(cacheManager) // Non-contiguous page (should be ignored) + apolloExceptionHandler = {} val query5 = UsersQuery(first = Optional.Present(2), after = Optional.Present("xx50")) val data5 = UsersQuery.Data { someField = buildSomeType { diff --git a/tests/pagination/src/commonTest/kotlin/CursorBasedPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/CursorBasedPaginationTest.kt index 32db066f0..8711f8a1e 100644 --- a/tests/pagination/src/commonTest/kotlin/CursorBasedPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/CursorBasedPaginationTest.kt @@ -2,6 +2,7 @@ package pagination import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.api.Optional +import com.apollographql.apollo.exception.apolloExceptionHandler import com.apollographql.apollo.testing.QueueTestNetworkTransport import com.apollographql.apollo.testing.enqueueTestResponse import com.apollographql.cache.normalized.CacheManager @@ -328,6 +329,7 @@ class CursorBasedPaginationTest { assertChainedCachesAreEqual(cacheManager) // Non-contiguous page (should be ignored) + apolloExceptionHandler = {} val query5 = UsersQuery(first = Optional.Present(2), after = Optional.Present("xx50")) val data5 = UsersQuery.Data { users = buildUserConnection {