diff --git a/normalized-cache-sqlite/api/android/normalized-cache-sqlite.api b/normalized-cache-sqlite/api/android/normalized-cache-sqlite.api index 23861ec42..d2f9ad229 100644 --- a/normalized-cache-sqlite/api/android/normalized-cache-sqlite.api +++ b/normalized-cache-sqlite/api/android/normalized-cache-sqlite.api @@ -18,6 +18,7 @@ public final class com/apollographql/cache/normalized/sql/SqlNormalizedCache : c 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 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 ea7be2788..95ca874a8 100644 --- a/normalized-cache-sqlite/api/jvm/normalized-cache-sqlite.api +++ b/normalized-cache-sqlite/api/jvm/normalized-cache-sqlite.api @@ -7,6 +7,7 @@ public final class com/apollographql/cache/normalized/sql/SqlNormalizedCache : c 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 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 fa940fdf4..42ac381f8 100644 --- a/normalized-cache-sqlite/api/normalized-cache-sqlite.klib.api +++ b/normalized-cache-sqlite/api/normalized-cache-sqlite.klib.api @@ -87,6 +87,7 @@ final class com.apollographql.cache.normalized.sql.internal.record/SelectRecords } final class com.apollographql.cache.normalized.sql/SqlNormalizedCache : com.apollographql.cache.normalized.api/NormalizedCache { // com.apollographql.cache.normalized.sql/SqlNormalizedCache|null[0] + final fun sizeOfRecord(com.apollographql.cache.normalized.api/Record): kotlin/Int // com.apollographql.cache.normalized.sql/SqlNormalizedCache.sizeOfRecord|sizeOfRecord(com.apollographql.cache.normalized.api.Record){}[0] final suspend fun clearAll() // com.apollographql.cache.normalized.sql/SqlNormalizedCache.clearAll|clearAll(){}[0] final suspend fun dump(): kotlin.collections/Map, kotlin.collections/Map> // com.apollographql.cache.normalized.sql/SqlNormalizedCache.dump|dump(){}[0] final 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.sql/SqlNormalizedCache.loadRecord|loadRecord(com.apollographql.cache.normalized.api.CacheKey;com.apollographql.cache.normalized.api.CacheHeaders){}[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 596b89d52..98b758185 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 @@ -9,7 +9,9 @@ import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.api.RecordMerger import com.apollographql.cache.normalized.api.RecordMergerContext import com.apollographql.cache.normalized.api.withDates +import com.apollographql.cache.normalized.api.withSizeInBytes import com.apollographql.cache.normalized.sql.internal.RecordDatabase +import com.apollographql.cache.normalized.sql.internal.RecordSerializer import com.apollographql.cache.normalized.sql.internal.parametersMax import kotlin.reflect.KClass @@ -80,7 +82,15 @@ class SqlNormalizedCache internal constructor( override suspend fun dump(): Map, Map> { recordDatabase.init() - return mapOf(this::class to recordDatabase.selectAllRecords().associateBy { it.key }) + return mapOf( + this::class to (recordDatabase.selectAllRecords().associateBy { it.key }) + .mapValues { (_, record) -> record.withSizeInBytes(sizeOfRecord(record)) }, + ) + } + + override fun sizeOfRecord(record: Record): Int { + val keySize = record.key.key.length + return keySize + RecordSerializer.serialize(record).size } private suspend fun getReferencedKeysRecursively( diff --git a/normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/internal/RecordSerializer.kt b/normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/internal/RecordSerializer.kt index 178e19510..e5a4fd7ec 100644 --- a/normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/internal/RecordSerializer.kt +++ b/normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/internal/RecordSerializer.kt @@ -62,17 +62,17 @@ internal object RecordSerializer { in Byte.MIN_VALUE..Byte.MAX_VALUE -> { writeByte(INT_BYTE) - writeByte(value.toInt()) + writeByte(value) } in Short.MIN_VALUE..Short.MAX_VALUE -> { writeByte(INT_SHORT) - writeShort(value.toInt()) + writeShort(value) } else -> { writeByte(INT_INT) - writeInt(value.toInt()) + writeInt(value) } } } @@ -111,7 +111,7 @@ internal object RecordSerializer { else -> { writeByte(LONG_LONG) - writeLong(value.toLong()) + writeLong(value) } } } @@ -146,17 +146,16 @@ internal object RecordSerializer { } } - is Int -> { - _writeInt(value) - } - - is Long -> { - _writeLong(value) - } - - is Double -> { - writeByte(DOUBLE) - writeLong(value.toBits()) + is Number -> { + // Using `is` on numbers always return true in JS, so we use the class instead + when (value::class) { + Int::class -> _writeInt(value as Int) + Long::class -> _writeLong(value as Long) + else -> { + writeByte(DOUBLE) + writeLong((value as Double).toBits()) + } + } } is JsonNumber -> { diff --git a/normalized-cache-sqlite/src/commonTest/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheTest.kt b/normalized-cache-sqlite/src/commonTest/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheTest.kt index 3e49625bf..54b3c6d57 100644 --- a/normalized-cache-sqlite/src/commonTest/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheTest.kt +++ b/normalized-cache-sqlite/src/commonTest/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheTest.kt @@ -6,6 +6,7 @@ import app.cash.sqldelight.db.QueryResult import app.cash.sqldelight.db.SqlCursor import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlPreparedStatement +import com.apollographql.apollo.api.json.JsonNumber import com.apollographql.apollo.exception.apolloExceptionHandler import com.apollographql.cache.normalized.api.ApolloCacheHeaders import com.apollographql.cache.normalized.api.CacheHeaders @@ -258,6 +259,35 @@ class SqlNormalizedCacheTest { assertNull(cache.loadRecord(CacheKey("key2"), CacheHeaders.NONE)) } + @Test + fun testSizeOfRecord() = runTest { + val expectedDouble = 1.23 + val expectedLongValue = Long.MAX_VALUE + val expectedStringValue = "StringValue" + val expectedBooleanValue = true + val expectedNumberValue = JsonNumber("10") + val expectedCacheKey = CacheKey("foo") + val expectedCacheKeyList = listOf(CacheKey("bar"), CacheKey("baz")) + val expectedScalarList = listOf("scalarOne", "scalarTwo") + val record = Record( + key = CacheKey("root"), + fields = mapOf( + "double" to expectedDouble, + "string" to expectedStringValue, + "boolean" to expectedBooleanValue, + "long" to expectedLongValue, + "number" to expectedNumberValue, + "cacheReference" to expectedCacheKey, + "scalarList" to expectedScalarList, + "referenceList" to expectedCacheKeyList, + ), + ) + + val normalizedCache = SqlNormalizedCacheFactory().create().apply { clearAll() } + val sizeOfRecord = normalizedCache.sizeOfRecord(record) + assertEquals(157, sizeOfRecord) + } + private val BadDriver = object : SqlDriver { override fun close() { throw IllegalStateException("bad cache") diff --git a/normalized-cache/api/normalized-cache.api b/normalized-cache/api/normalized-cache.api index 67292dbf1..39247d76d 100644 --- a/normalized-cache/api/normalized-cache.api +++ b/normalized-cache/api/normalized-cache.api @@ -611,6 +611,7 @@ public final class com/apollographql/cache/normalized/api/NormalizedCache$Compan } public final class com/apollographql/cache/normalized/api/NormalizedCache$DefaultImpls { + 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; } @@ -624,12 +625,17 @@ public abstract interface class com/apollographql/cache/normalized/api/ReadOnlyN public abstract fun dump (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 sizeOfRecord (Lcom/apollographql/cache/normalized/api/Record;)I +} + +public final class com/apollographql/cache/normalized/api/ReadOnlyNormalizedCache$DefaultImpls { + public static fun sizeOfRecord (Lcom/apollographql/cache/normalized/api/ReadOnlyNormalizedCache;Lcom/apollographql/cache/normalized/api/Record;)I } public final class com/apollographql/cache/normalized/api/Record : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { public static final field Companion Lcom/apollographql/cache/normalized/api/Record$Companion; + public synthetic fun (Ljava/lang/String;Ljava/util/Map;Ljava/util/UUID;Ljava/util/Map;IILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Ljava/lang/String;Ljava/util/Map;Ljava/util/UUID;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;Ljava/util/UUID;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun clear ()V public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; public fun compute (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Object; @@ -683,6 +689,7 @@ public final class com/apollographql/cache/normalized/api/RecordKt { public static final fun expirationDate (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;)Ljava/lang/Long; public static final fun receivedDate (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;)Ljava/lang/Long; public static final fun withDates (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/cache/normalized/api/Record; + public static final fun withSizeInBytes (Lcom/apollographql/cache/normalized/api/Record;I)Lcom/apollographql/cache/normalized/api/Record; } public abstract interface class com/apollographql/cache/normalized/api/RecordMerger { @@ -756,6 +763,7 @@ public final class com/apollographql/cache/normalized/memory/MemoryCache : com/a 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 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 7548c6ab3..2c5f03054 100644 --- a/normalized-cache/api/normalized-cache.klib.api +++ b/normalized-cache/api/normalized-cache.klib.api @@ -74,6 +74,7 @@ abstract interface com.apollographql.cache.normalized.api/ReadOnlyNormalizedCach 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] } abstract interface com.apollographql.cache.normalized.api/RecordMerger { // com.apollographql.cache.normalized.api/RecordMerger|null[0] @@ -318,7 +319,7 @@ final class com.apollographql.cache.normalized.api/MetadataGeneratorContext { // } final class com.apollographql.cache.normalized.api/Record : kotlin.collections/Map { // com.apollographql.cache.normalized.api/Record|null[0] - constructor (com.apollographql.cache.normalized.api/CacheKey, kotlin.collections/Map, com.benasher44.uuid/Uuid? = ..., kotlin.collections/Map> = ...) // com.apollographql.cache.normalized.api/Record.|(com.apollographql.cache.normalized.api.CacheKey;kotlin.collections.Map;com.benasher44.uuid.Uuid?;kotlin.collections.Map>){}[0] + constructor (com.apollographql.cache.normalized.api/CacheKey, kotlin.collections/Map, com.benasher44.uuid/Uuid? = ..., kotlin.collections/Map> = ..., kotlin/Int = ...) // com.apollographql.cache.normalized.api/Record.|(com.apollographql.cache.normalized.api.CacheKey;kotlin.collections.Map;com.benasher44.uuid.Uuid?;kotlin.collections.Map>;kotlin.Int){}[0] final val entries // com.apollographql.cache.normalized.api/Record.entries|{}entries[0] final fun (): kotlin.collections/Set> // com.apollographql.cache.normalized.api/Record.entries.|(){}[0] @@ -400,6 +401,7 @@ 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 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] final 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.memory/MemoryCache.loadRecord|loadRecord(com.apollographql.cache.normalized.api.CacheKey;com.apollographql.cache.normalized.api.CacheHeaders){}[0] @@ -673,6 +675,7 @@ final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cach final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/expirationDate(kotlin/String): kotlin/Long? // com.apollographql.cache.normalized.api/expirationDate|expirationDate@com.apollographql.cache.normalized.api.Record(kotlin.String){}[0] final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/receivedDate(kotlin/String): kotlin/Long? // com.apollographql.cache.normalized.api/receivedDate|receivedDate@com.apollographql.cache.normalized.api.Record(kotlin.String){}[0] final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/withDates(kotlin/String?, kotlin/String?): com.apollographql.cache.normalized.api/Record // com.apollographql.cache.normalized.api/withDates|withDates@com.apollographql.cache.normalized.api.Record(kotlin.String?;kotlin.String?){}[0] +final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/withSizeInBytes(kotlin/Int): com.apollographql.cache.normalized.api/Record // com.apollographql.cache.normalized.api/withSizeInBytes|withSizeInBytes@com.apollographql.cache.normalized.api.Record(kotlin.Int){}[0] final fun (com.apollographql.cache.normalized.api/ResolverContext).com.apollographql.cache.normalized.api/getFieldKey(): kotlin/String // com.apollographql.cache.normalized.api/getFieldKey|getFieldKey@com.apollographql.cache.normalized.api.ResolverContext(){}[0] final fun (kotlin.collections/Collection?).com.apollographql.cache.normalized.api/dependentKeys(): kotlin.collections/Set // com.apollographql.cache.normalized.api/dependentKeys|dependentKeys@kotlin.collections.Collection?(){}[0] final fun (kotlin.collections/Map).com.apollographql.cache.normalized/getReachableCacheKeys(): kotlin.collections/Set // com.apollographql.cache.normalized/getReachableCacheKeys|getReachableCacheKeys@kotlin.collections.Map(){}[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 43e5fa0ff..5e7259399 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 @@ -22,4 +22,10 @@ interface ReadOnlyNormalizedCache { suspend fun loadRecords(keys: Collection, cacheHeaders: CacheHeaders): Collection suspend fun dump(): Map<@JvmSuppressWildcards KClass<*>, Map> + + /** + * 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. + */ + fun sizeOfRecord(record: Record): Int = -1 } diff --git a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt index a6ee7c945..18aa3c7ec 100644 --- a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt +++ b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt @@ -2,7 +2,6 @@ package com.apollographql.cache.normalized.api import com.apollographql.apollo.annotations.ApolloInternal import com.apollographql.apollo.api.json.ApolloJsonElement -import com.apollographql.cache.normalized.internal.RecordWeigher.calculateBytes import com.benasher44.uuid.Uuid /** @@ -18,13 +17,13 @@ class Record( * Arbitrary metadata that can be attached to each field. */ val metadata: Map> = emptyMap(), -) : Map by fields { - - val sizeInBytes: Int - get() { - return calculateBytes(this) - } + /** + * Size of the record in bytes. This is an optional field that can be set by the cache implementation for debug purposes, otherwise it + * defaults to `-1`, meaning unknown size. + */ + val sizeInBytes: Int = -1, +) : Map by fields { /** * Returns a merge result record and a set of field keys which have changed, or were added. * A field key incorporates any GraphQL arguments in addition to the field name. @@ -98,11 +97,21 @@ fun Record.withDates(receivedDate: String?, expirationDate: String?): Record { ) } +@ApolloInternal +fun Record.withSizeInBytes(sizeInBytes: Int): Record { + return Record( + key = key, + fields = fields, + mutationId = mutationId, + metadata = metadata, + sizeInBytes = sizeInBytes, + ) +} + fun Record.receivedDate(field: String) = metadata[field]?.get(ApolloCacheHeaders.RECEIVED_DATE) as? Long fun Record.expirationDate(field: String) = metadata[field]?.get(ApolloCacheHeaders.EXPIRATION_DATE) as? Long - /** * A typealias for a type-unsafe Kotlin representation of a Record value. This typealias is * mainly for internal documentation purposes and low-level manipulations and should 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 b1ad50541..cca793439 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 @@ -104,6 +104,10 @@ internal class OptimisticNormalizedCache(private val wrapped: NormalizedCache) : return mapOf(this::class to recordJournals.mapValues { (_, journal) -> journal.current }) + wrapped.dump() } + override fun sizeOfRecord(record: Record): Int { + return wrapped.sizeOfRecord(record) + } + private fun Record?.mergeJournalRecord(key: CacheKey): Record? { val journal = recordJournals[key] return if (journal != null) { 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 ad458af53..ccff15564 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 @@ -9,8 +9,10 @@ import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.api.RecordMerger import com.apollographql.cache.normalized.api.RecordMergerContext import com.apollographql.cache.normalized.api.withDates +import com.apollographql.cache.normalized.api.withSizeInBytes import com.apollographql.cache.normalized.internal.withReentrantLock import com.apollographql.cache.normalized.memory.internal.LruCache +import com.apollographql.cache.normalized.memory.internal.RecordWeigher import kotlinx.coroutines.sync.Mutex import kotlin.reflect.KClass @@ -35,7 +37,7 @@ class MemoryCache( } private val lruCache = LruCache(maxSize = maxSizeBytes, expireAfterMillis = expireAfterMillis) { key, record -> - key.key.length + record.sizeInBytes + key.key.length + sizeOfRecord(record) } internal suspend fun getSize(): Int = withLock { lruCache.weight() } @@ -136,11 +138,15 @@ class MemoryCache( override suspend fun dump(): Map, Map> { return withLock { - mapOf(this::class to lruCache.asMap().mapValues { (_, record) -> record }) + + mapOf(this::class to lruCache.asMap().mapValues { (_, record) -> record.withSizeInBytes(sizeOfRecord(record)) }) + nextCache?.dump().orEmpty() } } + override fun sizeOfRecord(record: Record): Int { + return RecordWeigher.calculateBytes(record) + } + internal fun clearCurrentCache() { lruCache.clear() } diff --git a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/RecordWeigher.kt b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/internal/RecordWeigher.kt similarity index 86% rename from normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/RecordWeigher.kt rename to normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/internal/RecordWeigher.kt index 8129d27ad..3d4784483 100644 --- a/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/RecordWeigher.kt +++ b/normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/internal/RecordWeigher.kt @@ -1,11 +1,10 @@ -package com.apollographql.cache.normalized.internal +package com.apollographql.cache.normalized.memory.internal import com.apollographql.apollo.api.Error import com.apollographql.apollo.api.json.JsonNumber import com.apollographql.cache.normalized.api.CacheKey import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.api.RecordValue -import kotlin.jvm.JvmStatic internal object RecordWeigher { private const val SIZE_OF_BOOLEAN = 16 @@ -19,12 +18,6 @@ internal object RecordWeigher { private const val SIZE_OF_NULL = 4 private const val SIZE_OF_ERROR_OVERHEAD = 16 - @JvmStatic - fun byteChange(newValue: Any?, oldValue: Any?): Int { - return weighField(newValue) - weighField(oldValue) - } - - @JvmStatic fun calculateBytes(record: Record): Int { var size = SIZE_OF_RECORD_OVERHEAD + record.key.key.length for ((key, value) in record.fields) { @@ -43,9 +36,15 @@ internal object RecordWeigher { null -> SIZE_OF_NULL is String -> field.length is Boolean -> SIZE_OF_BOOLEAN - is Int -> SIZE_OF_INT - is Long -> SIZE_OF_LONG // Might happen with LongDataAdapter - is Double -> SIZE_OF_DOUBLE + is Number -> { + // Using `is` on numbers always return true in JS, so we use the class instead + when (field::class) { + Int::class -> SIZE_OF_INT + Long::class -> SIZE_OF_LONG + else -> SIZE_OF_DOUBLE + } + } + is JsonNumber -> field.value.length + SIZE_OF_LONG /** * Custom scalars with a json object representation are stored directly in the record 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 fc4fdd42c..51b039c72 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 @@ -1,5 +1,6 @@ package com.apollographql.cache.normalized +import com.apollographql.apollo.api.json.JsonNumber import com.apollographql.cache.normalized.api.ApolloCacheHeaders import com.apollographql.cache.normalized.api.CacheHeaders import com.apollographql.cache.normalized.api.CacheKey @@ -7,6 +8,7 @@ import com.apollographql.cache.normalized.api.DefaultRecordMerger import com.apollographql.cache.normalized.api.NormalizedCache import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.memory.MemoryCache +import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.testing.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -218,6 +220,17 @@ class MemoryCacheTest { } } + private fun assertEquals(expected: Record?, actual: Record?) { + assertTrue((expected == null) == (actual == null)) + if (expected == null || actual == null) { + return + } + assertEquals(expected.key, actual.key) + assertEquals(expected.fields, actual.fields) + assertEquals(expected.mutationId, actual.mutationId) + assertEquals(expected.metadata, actual.metadata) + } + @Test fun testRemove_cascadeFalse() = runTest { @@ -294,4 +307,33 @@ class MemoryCacheTest { ) ) } + + @Test + fun testRecordWeigher() { + val expectedDouble = 1.23 + val expectedLongValue = Long.MAX_VALUE + val expectedStringValue = "StringValue" + val expectedBooleanValue = true + val expectedNumberValue = JsonNumber("10") + val expectedCacheKey = CacheKey("foo") + val expectedCacheKeyList = listOf(CacheKey("bar"), CacheKey("baz")) + val expectedScalarList = listOf("scalarOne", "scalarTwo") + val record = Record( + key = CacheKey("root"), + fields = mapOf( + "double" to expectedDouble, + "string" to expectedStringValue, + "boolean" to expectedBooleanValue, + "long" to expectedLongValue, + "number" to expectedNumberValue, + "cacheReference" to expectedCacheKey, + "scalarList" to expectedScalarList, + "referenceList" to expectedCacheKeyList, + ), + ) + + val normalizedCache = MemoryCacheFactory().create() + val sizeOfRecord = normalizedCache.sizeOfRecord(record) + assertEquals(262, sizeOfRecord) + } } diff --git a/normalized-cache/src/commonTest/kotlin/com/apollographql/cache/normalized/RecordWeigherTest.kt b/normalized-cache/src/commonTest/kotlin/com/apollographql/cache/normalized/RecordWeigherTest.kt deleted file mode 100644 index 51cac0df7..000000000 --- a/normalized-cache/src/commonTest/kotlin/com/apollographql/cache/normalized/RecordWeigherTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.apollographql.cache.normalized - -import com.apollographql.apollo.api.json.JsonNumber -import com.apollographql.cache.normalized.api.CacheKey -import com.apollographql.cache.normalized.api.Record -import kotlin.test.Test -import kotlin.test.assertTrue - -class RecordWeigherTest { - - @Test - fun testRecordWeigher() { - val expectedDouble = 1.23 - val expectedLongValue = Long.MAX_VALUE - val expectedStringValue = "StringValue" - val expectedBooleanValue = true - val expectedNumberValue = JsonNumber("10") - val expectedCacheKey = CacheKey("foo") - val expectedCacheKeyList = listOf(CacheKey("bar"), CacheKey("baz")) - val expectedScalarList = listOf("scalarOne", "scalarTwo") - val record = Record( - key = CacheKey("root"), - fields = mapOf( - "double" to expectedDouble, - "string" to expectedStringValue, - "boolean" to expectedBooleanValue, - "long" to expectedLongValue, - "number" to expectedNumberValue, - "cacheReference" to expectedCacheKey, - "scalarList" to expectedScalarList, - "referenceList" to expectedCacheKeyList, - ) - ) - - assertTrue(record.sizeInBytes <= 284) - assertTrue(record.sizeInBytes >= 258) // JS takes less space, maybe for strings? - } -} diff --git a/normalized-cache/src/jvmCommonMain/kotlin/com/apollographql/cache/normalized/CacheManager.jvmCommon.kt b/normalized-cache/src/jvmCommonMain/kotlin/com/apollographql/cache/normalized/CacheManager.jvmCommon.kt index 609011795..afe84483d 100644 --- a/normalized-cache/src/jvmCommonMain/kotlin/com/apollographql/cache/normalized/CacheManager.jvmCommon.kt +++ b/normalized-cache/src/jvmCommonMain/kotlin/com/apollographql/cache/normalized/CacheManager.jvmCommon.kt @@ -12,7 +12,7 @@ internal actual fun CacheManager.cacheDumpProvider(): () -> Map key.keyToString() } .mapValues { (_, record) -> - record.size to ( + record.sizeInBytes to ( record.fields.mapValues { (_, value) -> value.toExternal() } + mapOf("__metadata" to record.metadata.toExternal()) diff --git a/tests/normalized-cache/src/commonTest/kotlin/ThreadTests.kt b/tests/normalized-cache/src/commonTest/kotlin/ThreadTests.kt index 312cf828a..130cd4e49 100644 --- a/tests/normalized-cache/src/commonTest/kotlin/ThreadTests.kt +++ b/tests/normalized-cache/src/commonTest/kotlin/ThreadTests.kt @@ -81,6 +81,10 @@ class ThreadTests { } return delegate.dump() } + + override fun sizeOfRecord(record: Record): Int { + return delegate.sizeOfRecord(record) + } } class MyMemoryCacheFactory(val mainThreadId: String) : NormalizedCacheFactory() { diff --git a/tests/schema-changes/src/commonTest/kotlin/test/SchemaChanges.kt b/tests/schema-changes/src/commonTest/kotlin/test/SchemaChanges.kt index 3b0e0886b..2bb5f7237 100644 --- a/tests/schema-changes/src/commonTest/kotlin/test/SchemaChanges.kt +++ b/tests/schema-changes/src/commonTest/kotlin/test/SchemaChanges.kt @@ -4,6 +4,7 @@ import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.api.CustomScalarAdapters import com.apollographql.apollo.api.json.jsonReader import com.apollographql.apollo.exception.CacheMissException +import com.apollographql.apollo.exception.apolloExceptionHandler import com.apollographql.cache.normalized.FetchPolicy import com.apollographql.cache.normalized.api.DefaultCacheKeyGenerator import com.apollographql.cache.normalized.api.DefaultCacheResolver @@ -78,6 +79,9 @@ class SchemaChangesTest { .fetchPolicy(FetchPolicy.NetworkOnly) .execute() + // Suppress default exception handler which confuses Apple tests + apolloExceptionHandler = {} + // Read v2 schema data from the cache val cacheResponse = apolloClient.query(schemav2.GetUserQuery()) .fetchPolicy(FetchPolicy.CacheOnly)