Skip to content

Add IdCacheKeyGenerator and IdCacheKeyResolver #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
- Expiration support (see [the documentation](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/expiration.html) for details)
- Compatibility with the IntelliJ plugin cache viewer (#42)
- For consistency, `MemoryCacheFactory` and `MemoryCache` are now in the `com.apollographql.cache.normalized.memory` package
- Remove deprecated symbols
- Remove deprecated symbols
- Add `IdCacheKeyGenerator` and `IdCacheKeyResolver` (#41)

# Version 0.0.3
_2024-09-20_
Expand Down
15 changes: 15 additions & 0 deletions normalized-cache-incubating/api/normalized-cache-incubating.api
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,21 @@ public final class com/apollographql/cache/normalized/api/GlobalMaxAgeProvider :
public fun getMaxAge-5sfh64U (Lcom/apollographql/cache/normalized/api/MaxAgeContext;)J
}

public final class com/apollographql/cache/normalized/api/IdCacheKeyGenerator : com/apollographql/cache/normalized/api/CacheKeyGenerator {
public fun <init> ()V
public fun <init> ([Ljava/lang/String;)V
public synthetic fun <init> ([Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun cacheKeyForObject (Ljava/util/Map;Lcom/apollographql/cache/normalized/api/CacheKeyGeneratorContext;)Lcom/apollographql/cache/normalized/api/CacheKey;
}

public final class com/apollographql/cache/normalized/api/IdCacheKeyResolver : com/apollographql/cache/normalized/api/CacheKeyResolver {
public fun <init> ()V
public fun <init> (Ljava/util/List;Ljava/util/List;)V
public synthetic fun <init> (Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun cacheKeyForField (Lcom/apollographql/cache/normalized/api/ResolverContext;)Lcom/apollographql/cache/normalized/api/CacheKey;
public fun listOfCacheKeysForField (Lcom/apollographql/cache/normalized/api/ResolverContext;)Ljava/util/List;
}

public abstract interface class com/apollographql/cache/normalized/api/MaxAge {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ final class com.apollographql.cache.normalized.api/GlobalMaxAgeProvider : com.ap
constructor <init>(kotlin.time/Duration) // com.apollographql.cache.normalized.api/GlobalMaxAgeProvider.<init>|<init>(kotlin.time.Duration){}[0]
final fun getMaxAge(com.apollographql.cache.normalized.api/MaxAgeContext): kotlin.time/Duration // com.apollographql.cache.normalized.api/GlobalMaxAgeProvider.getMaxAge|getMaxAge(com.apollographql.cache.normalized.api.MaxAgeContext){}[0]
}
final class com.apollographql.cache.normalized.api/IdCacheKeyGenerator : com.apollographql.cache.normalized.api/CacheKeyGenerator { // com.apollographql.cache.normalized.api/IdCacheKeyGenerator|null[0]
constructor <init>(kotlin/Array<out kotlin/String>... = ...) // com.apollographql.cache.normalized.api/IdCacheKeyGenerator.<init>|<init>(kotlin.Array<out|kotlin.String>...){}[0]
final fun cacheKeyForObject(kotlin.collections/Map<kotlin/String, kotlin/Any?>, com.apollographql.cache.normalized.api/CacheKeyGeneratorContext): com.apollographql.cache.normalized.api/CacheKey? // com.apollographql.cache.normalized.api/IdCacheKeyGenerator.cacheKeyForObject|cacheKeyForObject(kotlin.collections.Map<kotlin.String,kotlin.Any?>;com.apollographql.cache.normalized.api.CacheKeyGeneratorContext){}[0]
}
final class com.apollographql.cache.normalized.api/IdCacheKeyResolver : com.apollographql.cache.normalized.api/CacheKeyResolver { // com.apollographql.cache.normalized.api/IdCacheKeyResolver|null[0]
constructor <init>(kotlin.collections/List<kotlin/String> = ..., kotlin.collections/List<kotlin/String> = ...) // com.apollographql.cache.normalized.api/IdCacheKeyResolver.<init>|<init>(kotlin.collections.List<kotlin.String>;kotlin.collections.List<kotlin.String>){}[0]
final fun cacheKeyForField(com.apollographql.cache.normalized.api/ResolverContext): com.apollographql.cache.normalized.api/CacheKey? // com.apollographql.cache.normalized.api/IdCacheKeyResolver.cacheKeyForField|cacheKeyForField(com.apollographql.cache.normalized.api.ResolverContext){}[0]
final fun listOfCacheKeysForField(com.apollographql.cache.normalized.api/ResolverContext): kotlin.collections/List<com.apollographql.cache.normalized.api/CacheKey?>? // com.apollographql.cache.normalized.api/IdCacheKeyResolver.listOfCacheKeysForField|listOfCacheKeysForField(com.apollographql.cache.normalized.api.ResolverContext){}[0]
}
final class com.apollographql.cache.normalized.api/MaxAgeContext { // com.apollographql.cache.normalized.api/MaxAgeContext|null[0]
constructor <init>(kotlin.collections/List<com.apollographql.apollo.api/CompiledField>) // com.apollographql.cache.normalized.api/MaxAgeContext.<init>|<init>(kotlin.collections.List<com.apollographql.apollo.api.CompiledField>){}[0]
final val fieldPath // com.apollographql.cache.normalized.api/MaxAgeContext.fieldPath|{}fieldPath[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class CacheKeyGeneratorContext(
)

/**
* A [CacheKeyGenerator] that uses annotations to compute the id
* A [CacheKeyGenerator] that uses the `@typePolicy` directive to compute the id
*/
object TypePolicyCacheKeyGenerator : CacheKeyGenerator {
override fun cacheKeyForObject(obj: Map<String, Any?>, context: CacheKeyGeneratorContext): CacheKey? {
Expand All @@ -54,3 +54,19 @@ object TypePolicyCacheKeyGenerator : CacheKeyGenerator {
}
}
}

/**
* A [CacheKeyGenerator] that uses the given id fields to compute the cache key.
* If the id field(s) is/are missing, the object is considered to not have an id.
*
* @see IdCacheKeyResolver
*/
class IdCacheKeyGenerator(private vararg val idFields: String = arrayOf("id")) : CacheKeyGenerator {
override fun cacheKeyForObject(obj: Map<String, Any?>, context: CacheKeyGeneratorContext): CacheKey? {
val values = idFields.map {
(obj[it] ?: return null).toString()
}
val typeName = context.field.type.rawType().name
return CacheKey(typeName, values)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,32 @@ abstract class CacheKeyResolver : CacheResolver {
return DefaultCacheResolver.resolveField(context)
}
}

/**
* A simple [CacheKeyResolver] that uses the id/ids argument, if present, to compute the cache key.
* The name of the id arguments can be provided (by default "id" for objects and "ids" for lists).
* If several names are provided, the first present one is used.
* Only one level of list is supported - implement [CacheResolver] if you need arbitrary nested lists of objects.
*
* @param idFields possible names of the argument containing the id for objects
* @param idListFields possible names of the argument containing the ids for lists
*
* @see IdCacheKeyGenerator
*/
class IdCacheKeyResolver(
private val idFields: List<String> = listOf("id"),
private val idListFields: List<String> = listOf("ids"),
) : CacheKeyResolver() {
override fun cacheKeyForField(context: ResolverContext): CacheKey? {
val typeName = context.field.type.rawType().name
val id = idFields.firstNotNullOfOrNull { context.field.argumentValue(it, context.variables).getOrNull()?.toString() } ?: return null
return CacheKey(typeName, id)
}

override fun listOfCacheKeysForField(context: ResolverContext): List<CacheKey?>? {
val typeName = context.field.type.rawType().name
val ids = idListFields.firstNotNullOfOrNull { context.field.argumentValue(it, context.variables).getOrNull() as? List<*> }
?: return null
return ids.map { id -> id?.toString()?.let { CacheKey(typeName, it) } }
}
}
2 changes: 1 addition & 1 deletion tests/normalized-cache/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ kotlin {

apollo {
service("service") {
packageName.set("sqlite")
packageName.set("test")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,38 @@ query GetUser {
}
}

query GetUser2($id: ID!) {
user2(id: $id) {
id
name
email
}
}

query GetUserById($userId: ID!) {
userById(userId: $userId) {
userId
name
email
}
}

query GetUsers($ids: [ID!]!) {
users(ids: $ids) {
id
name
email
}
}

query GetUsersByIDs($userIds: [ID!]!) {
usersByIDs(userIds: $userIds) {
id
name
email
}
}

query RepositoryListQuery($first: Int = 15, $after: String) {
repositories(first: $first, after: $after) {
id
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
type Query {
user: User
user2(id: ID!): User
userById(userId: ID!): User
users(ids: [ID!]!): [User!]!
usersByIDs(userIds: [ID!]!): [User!]!
repositories(first: Int, after: String): [Repository!]!
}

type User {
id: ID!
userId: ID!
name: String!
email: String!
admin: Boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package test

import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.testing.QueueTestNetworkTransport
import com.apollographql.apollo.testing.enqueueTestResponse
import com.apollographql.apollo.testing.internal.runTest
import com.apollographql.cache.normalized.ApolloStore
import com.apollographql.cache.normalized.FetchPolicy
import com.apollographql.cache.normalized.api.IdCacheKeyGenerator
import com.apollographql.cache.normalized.api.IdCacheKeyResolver
import com.apollographql.cache.normalized.fetchPolicy
import com.apollographql.cache.normalized.memory.MemoryCacheFactory
import com.apollographql.cache.normalized.store
import kotlin.test.Test
import kotlin.test.assertEquals

class IdCacheKeyGeneratorTest {
@Test
fun defaultValues() = runTest {
val store = ApolloStore(
normalizedCacheFactory = MemoryCacheFactory(),
cacheKeyGenerator = IdCacheKeyGenerator(),
cacheResolver = IdCacheKeyResolver(),
)
val apolloClient = ApolloClient.Builder().networkTransport(QueueTestNetworkTransport()).store(store).build()
val query = GetUser2Query("42")
apolloClient.enqueueTestResponse(query, GetUser2Query.Data(GetUser2Query.User2(id = "42", name = "John", email = "a@a.com")))
apolloClient.query(query).fetchPolicy(FetchPolicy.NetworkOnly).execute()
val user = apolloClient.query(query).fetchPolicy(FetchPolicy.CacheOnly).execute().dataOrThrow().user2!!
assertEquals("John", user.name)
assertEquals("a@a.com", user.email)
}

@Test
fun customIdField() = runTest {
val store = ApolloStore(
normalizedCacheFactory = MemoryCacheFactory(),
cacheKeyGenerator = IdCacheKeyGenerator("userId"),
cacheResolver = IdCacheKeyResolver(idFields = listOf("userId")),
)
val apolloClient = ApolloClient.Builder().networkTransport(QueueTestNetworkTransport()).store(store).build()
val query = GetUserByIdQuery("42")
apolloClient.enqueueTestResponse(query, GetUserByIdQuery.Data(GetUserByIdQuery.UserById(userId = "42", name = "John", email = "a@a.com")))
apolloClient.query(query).fetchPolicy(FetchPolicy.NetworkOnly).execute()
val user = apolloClient.query(query).fetchPolicy(FetchPolicy.CacheOnly).execute().dataOrThrow().userById!!
assertEquals("John", user.name)
assertEquals("a@a.com", user.email)
}

@Test
fun lists() = runTest {
val store = ApolloStore(
normalizedCacheFactory = MemoryCacheFactory(),
cacheKeyGenerator = IdCacheKeyGenerator("id"),
cacheResolver = IdCacheKeyResolver(idListFields = listOf("ids", "userIds")),
)
val apolloClient = ApolloClient.Builder().networkTransport(QueueTestNetworkTransport()).store(store).build()
val query1 = GetUsersQuery(listOf("42", "43"))
apolloClient.enqueueTestResponse(
query1,
GetUsersQuery.Data(
listOf(
GetUsersQuery.User(id = "42", name = "John", email = "a@a.com"),
GetUsersQuery.User(id = "43", name = "Jane", email = "b@b.com"),
)
)
)
apolloClient.query(query1).fetchPolicy(FetchPolicy.NetworkOnly).execute()
val users1 = apolloClient.query(query1).fetchPolicy(FetchPolicy.CacheOnly).execute().dataOrThrow().users
assertEquals(2, users1.size)
assertEquals("John", users1[0].name)
assertEquals("a@a.com", users1[0].email)
assertEquals("Jane", users1[1].name)
assertEquals("b@b.com", users1[1].email)

val query2 = GetUsersByIDsQuery(listOf("42", "43"))
val users2 = apolloClient.query(query2).fetchPolicy(FetchPolicy.CacheOnly).execute().dataOrThrow().usersByIDs
assertEquals(2, users2.size)
assertEquals("John", users2[0].name)
assertEquals("a@a.com", users2[0].email)
assertEquals("Jane", users2[1].name)
assertEquals("b@b.com", users2[1].email)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import com.apollographql.cache.normalized.memoryCacheOnly
import com.apollographql.cache.normalized.sql.SqlNormalizedCache
import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory
import com.apollographql.cache.normalized.store
import sqlite.GetUserQuery
import kotlin.reflect.KClass
import kotlin.test.Test
import kotlin.test.assertEquals
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.apollographql.cache.normalized.FetchPolicy
import com.apollographql.cache.normalized.fetchPolicy
import com.apollographql.cache.normalized.memory.MemoryCacheFactory
import com.apollographql.cache.normalized.normalizedCache
import sqlite.RepositoryListQuery
import kotlin.test.Test
import kotlin.test.assertEquals

Expand Down