From 4f3e62714eb931769ffe458050c81e0fc82bc353 Mon Sep 17 00:00:00 2001 From: BoD Date: Thu, 19 Dec 2024 15:19:26 +0100 Subject: [PATCH] Migrate legacy json format to blob format by dropping the base. --- gradle/libs.versions.toml | 2 + .../build.gradle.kts | 17 +- .../sqldelight/blob/schema/2.db | Bin 0 -> 8192 bytes .../sqldelight/json/schema/1.db | Bin 16384 -> 0 bytes .../sqldelight/blob/migrations/1.sqm | 7 + tests/migration/build.gradle.kts | 38 ++++ .../src/commonMain/graphql/extra.graphqls | 13 ++ .../src/commonMain/graphql/operations.graphql | 60 ++++++ .../src/commonMain/graphql/schema.graphqls | 39 ++++ .../src/commonTest/kotlin/MigrationTest.kt | 182 ++++++++++++++++++ 10 files changed, 347 insertions(+), 11 deletions(-) create mode 100644 normalized-cache-sqlite-incubating/sqldelight/blob/schema/2.db delete mode 100644 normalized-cache-sqlite-incubating/sqldelight/json/schema/1.db create mode 100644 normalized-cache-sqlite-incubating/src/commonMain/sqldelight/blob/migrations/1.sqm create mode 100644 tests/migration/build.gradle.kts create mode 100644 tests/migration/src/commonMain/graphql/extra.graphqls create mode 100644 tests/migration/src/commonMain/graphql/operations.graphql create mode 100644 tests/migration/src/commonMain/graphql/schema.graphqls create mode 100644 tests/migration/src/commonTest/kotlin/MigrationTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4b1acf41..ab580114 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,8 @@ apollo-runtime = { group = "com.apollographql.apollo", name = "apollo-runtime", apollo-mockserver = "com.apollographql.mockserver:apollo-mockserver:0.0.1" apollo-ast = { group = "com.apollographql.apollo", name = "apollo-ast", version.ref = "apollo" } apollo-compiler = { group = "com.apollographql.apollo", name = "apollo-compiler", version.ref = "apollo" } +apollo-cache = { group = "com.apollographql.apollo", name = "apollo-normalized-cache", version.ref = "apollo" } +apollo-cache-sqlite = { group = "com.apollographql.apollo", name = "apollo-normalized-cache-sqlite", version.ref = "apollo" } atomicfu-library = { group = "org.jetbrains.kotlinx", name = "atomicfu", version.ref = "atomicfu" } kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test" } # the Kotlin plugin resolves the version kotlin-test-junit = { group = "org.jetbrains.kotlin", name = "kotlin-test-junit" } # the Kotlin plugin resolves the version diff --git a/normalized-cache-sqlite-incubating/build.gradle.kts b/normalized-cache-sqlite-incubating/build.gradle.kts index 99db6085..96921424 100644 --- a/normalized-cache-sqlite-incubating/build.gradle.kts +++ b/normalized-cache-sqlite-incubating/build.gradle.kts @@ -31,20 +31,15 @@ android { configure { - databases.create("JsonDatabase") { - packageName = "com.apollographql.cache.normalized.sql.internal.json" - schemaOutputDirectory = file("sqldelight/json/schema") - srcDirs("src/commonMain/sqldelight/json/") - } databases.create("BlobDatabase") { - packageName = "com.apollographql.cache.normalized.sql.internal.blob" - schemaOutputDirectory = file("sqldelight/blob/schema") - srcDirs("src/commonMain/sqldelight/blob/") + packageName.set("com.apollographql.cache.normalized.sql.internal.blob") + schemaOutputDirectory.set(file("sqldelight/blob/schema")) + srcDirs.setFrom("src/commonMain/sqldelight/blob/") } databases.create("Blob2Database") { - packageName = "com.apollographql.cache.normalized.sql.internal.blob2" - schemaOutputDirectory = file("sqldelight/blob2/schema") - srcDirs("src/commonMain/sqldelight/blob2/") + packageName.set("com.apollographql.cache.normalized.sql.internal.blob2") + schemaOutputDirectory.set(file("sqldelight/blob2/schema")) + srcDirs.setFrom("src/commonMain/sqldelight/blob2/") } } diff --git a/normalized-cache-sqlite-incubating/sqldelight/blob/schema/2.db b/normalized-cache-sqlite-incubating/sqldelight/blob/schema/2.db new file mode 100644 index 0000000000000000000000000000000000000000..17bc7efe178cf33f29a58d1195823951afc38ba1 GIT binary patch literal 8192 zcmeIuu?oU46a~2d7J&$a5k;p1JmNe7c&O~0SG_< z0uX=z1Rwwb2tWV=5P-mq_g&ZJPp?k1BF~C4ySJCMP{PxKwC77f?H7$~qD%fDEsmPD zp-Chq?NqSwR6=W!OiZDvk}pI5+pxzJ2YMAonp7AEOS@5bKo0>3KmY;|fB*y_009U< M00Izzz&{Io09^qnZU6uP literal 0 HcmV?d00001 diff --git a/normalized-cache-sqlite-incubating/sqldelight/json/schema/1.db b/normalized-cache-sqlite-incubating/sqldelight/json/schema/1.db deleted file mode 100644 index 3c890c2172e19065dee6e72b51c6d908901d4068..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI%zfZzI6u|K-5H%!<8w1NrTp$n^{{gFB46zifC4s3ydYVRlgn|i<{3-rT{zD#G z20A!7k?$p!>s{Zywx4!+>C>Z^tc<+O)9Gj>SL$3jj=C06N)>D`*}iM}xwAX*$5>F8 z>F22pD4u<(%6sL)4m1Q1KmY**5I_I{1Q0*~0R;X-z%L$En>EKIv)HVYcpar?JWu0g z^kzP?!_sY_-B3&0ztaQB#YoqeEUs?1Gu5i0*+6^O66!%He%JQC=WQ2%GwrXJ8{6947X35?5I_I{1Q0*~0R#|0 z009ILIA8(l{{w!xJQ@NBAb + mockServer.enqueueString(REPOSITORY_LIST_RESPONSE) + apolloClient.query(RepositoryListQuery()) + .fetchPolicy(FetchPolicy.NetworkOnly) + .execute() + } + + // Open the legacy store which empties it. Add/read some data to make sure it works. + val store = ApolloStore(MemoryCacheFactory().chain(SqlNormalizedCacheFactory(name = name))) + ApolloClient.Builder() + .serverUrl(mockServer.url()) + .store(store) + .build() + .use { apolloClient -> + // Expected cache miss: the db has been cleared + var response = apolloClient.query(RepositoryListQuery()) + .fetchPolicy(FetchPolicy.CacheOnly) + .execute() + assertIs(response.exception) + + // Add some data + mockServer.enqueueString(REPOSITORY_LIST_RESPONSE) + apolloClient.query(RepositoryListQuery()) + .fetchPolicy(FetchPolicy.NetworkOnly) + .execute() + + // Read the data back + response = apolloClient.query(RepositoryListQuery()) + .fetchPolicy(FetchPolicy.CacheOnly) + .execute() + assertEquals(REPOSITORY_LIST_DATA, response.data) + + // Clean up + store.clearAll() + } + } + + @Test + fun migrateDb() = runTest { + val mockServer = MockServer() + // Create a legacy store with some data + val legacyStore = LegacyApolloStore(LegacySqlNormalizedCacheFactory(name = "legacy.db")).also { it.clearAll() } + ApolloClient.Builder() + .serverUrl(mockServer.url()) + .legacyStore(legacyStore) + .build() + .use { apolloClient -> + mockServer.enqueueString(REPOSITORY_LIST_RESPONSE) + apolloClient.query(RepositoryListQuery()) + .fetchPolicy(FetchPolicy.NetworkOnly) + .execute() + } + + // Create a modern store and migrate the legacy data + val store = ApolloStore(SqlNormalizedCacheFactory(name = "modern.db")).also { it.clearAll() } + store.migrateFrom(legacyStore) + + // Read the data back + ApolloClient.Builder() + .serverUrl(mockServer.url()) + .store(store) + .build() + .use { apolloClient -> + val response = apolloClient.query(RepositoryListQuery()) + .fetchPolicy(FetchPolicy.CacheOnly) + .execute() + assertEquals(REPOSITORY_LIST_DATA, response.data) + } + } +} + +private fun ApolloStore.migrateFrom(legacyStore: LegacyApolloStore) { + accessCache { cache -> + cache.merge( + records = legacyStore.accessCache { it.allRecords() }.map { it.toRecord() }, + cacheHeaders = CacheHeaders.NONE, + recordMerger = DefaultRecordMerger, + ) + } +} + +private fun LegacyNormalizedCache.allRecords(): List { + return dump().values.fold(emptyList()) { acc, map -> acc + map.values } +} + +private fun LegacyRecord.toRecord(): Record = Record( + key = key, + fields = fields.mapValues { (_, value) -> value.toRecordValue() }, + mutationId = mutationId +) + +private fun LegacyRecordValue.toRecordValue(): RecordValue = when (this) { + is Map<*, *> -> mapValues { (_, value) -> value.toRecordValue() } + is List<*> -> map { it.toRecordValue() } + is LegacyCacheKey -> CacheKey(key) + else -> this +}