From f43915dbbd06f68ce1c43c6cef25271cd253ba1a Mon Sep 17 00:00:00 2001 From: Benoit 'BoD' Lubek Date: Wed, 11 Sep 2024 09:48:19 +0200 Subject: [PATCH] Allow to store JsonNumber in Record (#36) --- .../cache/normalized/api/Record.kt | 14 ++++++++++- .../api/internal/BlobRecordSerializer.kt | 20 +++++++++++----- .../normalized/api/internal/Normalizer.kt | 9 ++++++-- .../normalized/api/internal/RecordWeigher.kt | 23 +++++++++++-------- .../cache/normalized/RecordWeigherTest.kt | 7 ++++-- 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt index 838d070..0362193 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt @@ -23,7 +23,7 @@ class Record( * - Map (for custom scalars) * - null */ - val fields: Map, + val fields: Map, val mutationId: Uuid? = null, ) : Map by fields { @@ -126,3 +126,15 @@ fun Record.receivedDate(field: String) = metadata[field]?.get(ApolloCacheHeaders @ApolloInternal 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 + * generally be avoided in application code. + * + * [RecordValue] can be any of: + * - [com.apollographql.apollo.api.json.ApolloJsonElement] + * - [CacheKey] + */ +typealias RecordValue = Any? diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/BlobRecordSerializer.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/BlobRecordSerializer.kt index af7ed2e..2fa746d 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/BlobRecordSerializer.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/BlobRecordSerializer.kt @@ -1,6 +1,7 @@ package com.apollographql.cache.normalized.api.internal import com.apollographql.apollo.annotations.ApolloInternal +import com.apollographql.apollo.api.json.JsonNumber import com.apollographql.cache.normalized.api.CacheKey import com.apollographql.cache.normalized.api.Record import okio.Buffer @@ -94,7 +95,12 @@ object BlobRecordSerializer { is Double -> { buffer.writeByte(DOUBLE) - buffer.writeString(value.toString()) + buffer.writeLong(value.toBits()) + } + + is JsonNumber -> { + buffer.writeByte(JSON_NUMBER) + buffer.writeString(value.value) } is Boolean -> { @@ -139,7 +145,8 @@ object BlobRecordSerializer { STRING -> readString() INT -> readInt() LONG -> readLong() - DOUBLE -> readString().toDouble() + DOUBLE -> Double.fromBits(readLong()) + JSON_NUMBER -> JsonNumber(readString()) BOOLEAN -> readByte() > 0 CACHE_KEY -> { CacheKey(readString()) @@ -169,8 +176,9 @@ object BlobRecordSerializer { private const val LONG = 2 private const val BOOLEAN = 3 private const val DOUBLE = 4 - private const val LIST = 5 - private const val MAP = 6 - private const val CACHE_KEY = 7 - private const val NULL = 8 + private const val JSON_NUMBER = 5 + private const val LIST = 6 + private const val MAP = 7 + private const val CACHE_KEY = 8 + private const val NULL = 9 } diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/Normalizer.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/Normalizer.kt index cda97df..387883c 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/Normalizer.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/Normalizer.kt @@ -9,6 +9,7 @@ import com.apollographql.apollo.api.CompiledSelection import com.apollographql.apollo.api.CompiledType import com.apollographql.apollo.api.Executable import com.apollographql.apollo.api.isComposite +import com.apollographql.apollo.api.json.ApolloJsonElement import com.apollographql.cache.normalized.api.CacheKey import com.apollographql.cache.normalized.api.CacheKeyGenerator import com.apollographql.cache.normalized.api.CacheKeyGeneratorContext @@ -34,7 +35,11 @@ internal class Normalizer( ) { private val records = mutableMapOf() - fun normalize(map: Map, selections: List, parentType: CompiledNamedType): Map { + fun normalize( + map: Map, + selections: List, + parentType: CompiledNamedType, + ): Map { buildRecord(map, rootKey, selections, parentType) return records @@ -114,7 +119,7 @@ internal class Normalizer( * @return the CacheKey if this object has a CacheKey or the new Map if the object was embedded */ private fun buildRecord( - obj: Map, + obj: Map, key: String, selections: List, parentType: CompiledNamedType, diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/RecordWeigher.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/RecordWeigher.kt index 0677da7..1964564 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/RecordWeigher.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/internal/RecordWeigher.kt @@ -1,7 +1,10 @@ package com.apollographql.cache.normalized.api.internal +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 okio.internal.commonAsUtf8ToByteArray import kotlin.jvm.JvmStatic internal object RecordWeigher { @@ -30,21 +33,15 @@ internal object RecordWeigher { return size } - private fun weighField(field: Any?): Int { + private fun weighField(field: RecordValue): Int { return when (field) { null -> SIZE_OF_NULL - is String -> field.length + is String -> field.commonAsUtf8ToByteArray().size 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 List<*> -> { - SIZE_OF_ARRAY_OVERHEAD + field.sumOf { weighField(it) } - } - - is CacheKey -> { - SIZE_OF_CACHE_KEY_OVERHEAD + field.key.length - } + is JsonNumber -> field.value.commonAsUtf8ToByteArray().size + SIZE_OF_LONG /** * Custom scalars with a json object representation are stored directly in the record */ @@ -52,6 +49,14 @@ internal object RecordWeigher { SIZE_OF_MAP_OVERHEAD + field.keys.sumOf { weighField(it) } + field.values.sumOf { weighField(it) } } + is List<*> -> { + SIZE_OF_ARRAY_OVERHEAD + field.sumOf { weighField(it) } + } + + is CacheKey -> { + SIZE_OF_CACHE_KEY_OVERHEAD + field.key.commonAsUtf8ToByteArray().size + } + else -> error("Unknown field type in Record: '$field'") } } diff --git a/normalized-cache-incubating/src/commonTest/kotlin/com/apollographql/cache/normalized/RecordWeigherTest.kt b/normalized-cache-incubating/src/commonTest/kotlin/com/apollographql/cache/normalized/RecordWeigherTest.kt index 34633d7..bbb949e 100644 --- a/normalized-cache-incubating/src/commonTest/kotlin/com/apollographql/cache/normalized/RecordWeigherTest.kt +++ b/normalized-cache-incubating/src/commonTest/kotlin/com/apollographql/cache/normalized/RecordWeigherTest.kt @@ -1,5 +1,6 @@ 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 @@ -13,6 +14,7 @@ class RecordWeigherTest { 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") @@ -23,13 +25,14 @@ class RecordWeigherTest { "string" to expectedStringValue, "boolean" to expectedBooleanValue, "long" to expectedLongValue, + "number" to expectedNumberValue, "cacheReference" to expectedCacheKey, "scalarList" to expectedScalarList, "referenceList" to expectedCacheKeyList, ) ) - assertTrue(record.sizeInBytes <= 248) - assertTrue(record.sizeInBytes >= 242) // JS takes less space, maybe for strings? + assertTrue(record.sizeInBytes <= 284) + assertTrue(record.sizeInBytes >= 258) // JS takes less space, maybe for strings? } }