Skip to content

Commit 95cbdc7

Browse files
authored
Merge branch 'main' into perf/defer-watcher-dependent-keys
2 parents 810fc22 + 0c23a5f commit 95cbdc7

18 files changed

Lines changed: 231 additions & 68 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
- [Improvement] Derive the keys a watcher matches cache changes against lazily, keeping the re-normalization of the whole response off the path that delivers the initial responses of `watch` (#378)
44
- [Improvement] Reduce allocations when deriving a watcher's dependent keys and matching them against a cache change (#378)
5+
- Enable parallel sync for Tooling API clients (#380)
56

67
PUT_CHANGELOG_HERE
78

9+
- [Fix] `SqlNormalizedCacheFactory()` no longer touches the disk when called, fixing a StrictMode main thread disk read. The driver is now created in `NormalizedCacheFactory.create()` (#373)
10+
811
# v1.0.6
912
_2026-07-17_
1013

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ android.useAndroidX=true
44

55
org.gradle.caching=true
66
org.gradle.parallel=true
7+
org.gradle.tooling.parallel=true
78

89
kotlin.stdlib.default.dependency=false

normalized-cache-sqlite/src/androidMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheFactory.android.kt

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
77
import app.cash.sqldelight.async.coroutines.synchronous
88
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
99
import com.apollographql.apollo.exception.apolloExceptionHandler
10+
import com.apollographql.cache.normalized.api.NormalizedCache
1011
import com.apollographql.cache.normalized.api.NormalizedCacheFactory
12+
import com.apollographql.cache.normalized.sql.internal.RecordDatabase
1113
import com.apollographql.cache.normalized.sql.internal.record.SqlRecordDatabase
1214

1315
actual fun SqlNormalizedCacheFactory(name: String?): NormalizedCacheFactory =
@@ -30,50 +32,50 @@ fun SqlNormalizedCacheFactory(
3032
configure: ((SupportSQLiteDatabase) -> Unit)? = null,
3133
useNoBackupDirectory: Boolean = false,
3234
windowSizeBytes: Long? = 4 * 1024 * 1024,
33-
): NormalizedCacheFactory {
34-
val synchronousSchema = SqlRecordDatabase.Schema.synchronous()
35-
val filePath = when {
36-
name == null -> {
37-
null
38-
}
35+
): NormalizedCacheFactory = object : NormalizedCacheFactory() {
36+
override fun create(): NormalizedCache {
37+
val synchronousSchema = SqlRecordDatabase.Schema.synchronous()
38+
val filePath = when {
39+
name == null -> {
40+
null
41+
}
3942

40-
name.startsWith("/") -> {
41-
// Absolute path: keep as-is
42-
name
43-
}
43+
name.startsWith("/") -> {
44+
// Absolute path: keep as-is
45+
name
46+
}
4447

45-
else -> {
46-
// Old versions of the library used to store the database in the database directory.
47-
// If such file exists, use it, otherwise, use the cache directory.
48-
(context.getDatabasePath(name).takeIf { it.exists() } ?: context.cacheDir.resolve(name)).absolutePath
48+
else -> {
49+
// Old versions of the library used to store the database in the database directory.
50+
// If such file exists, use it, otherwise, use the cache directory.
51+
(context.getDatabasePath(name).takeIf { it.exists() } ?: context.cacheDir.resolve(name)).absolutePath
52+
}
4953
}
50-
}
51-
return SqlNormalizedCacheFactory(
52-
driver = AndroidSqliteDriver(
53-
schema = synchronousSchema,
54-
context = context.applicationContext,
55-
name = filePath,
56-
factory = factory,
57-
callback = object : AndroidSqliteDriver.Callback(synchronousSchema) {
58-
override fun onConfigure(db: SupportSQLiteDatabase) {
59-
super.onConfigure(db)
60-
configure?.invoke(db)
61-
}
54+
val driver = AndroidSqliteDriver(
55+
schema = synchronousSchema,
56+
context = context.applicationContext,
57+
name = filePath,
58+
factory = factory,
59+
callback = object : AndroidSqliteDriver.Callback(synchronousSchema) {
60+
override fun onConfigure(db: SupportSQLiteDatabase) {
61+
super.onConfigure(db)
62+
configure?.invoke(db)
63+
}
6264

63-
override fun onCorruption(db: SupportSQLiteDatabase) {
64-
apolloExceptionHandler(Exception("Corruption detected, recreating the database"))
65-
super.onCorruption(db)
66-
}
65+
override fun onCorruption(db: SupportSQLiteDatabase) {
66+
apolloExceptionHandler(Exception("Corruption detected, recreating the database"))
67+
super.onCorruption(db)
68+
}
6769

68-
override fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
69-
// Treat downgrades as corruption, which results in a clean database
70-
apolloExceptionHandler(Exception("onDowngrade from $oldVersion to $newVersion, treating as database corruption"))
71-
super.onCorruption(db)
72-
}
73-
},
74-
useNoBackupDirectory = useNoBackupDirectory,
75-
windowSizeBytes = windowSizeBytes,
76-
),
77-
name = filePath,
78-
)
70+
override fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
71+
// Treat downgrades as corruption, which results in a clean database
72+
apolloExceptionHandler(Exception("onDowngrade from $oldVersion to $newVersion, treating as database corruption"))
73+
super.onCorruption(db)
74+
}
75+
},
76+
useNoBackupDirectory = useNoBackupDirectory,
77+
windowSizeBytes = windowSizeBytes,
78+
)
79+
return SqlNormalizedCache(RecordDatabase(driver, filePath))
80+
}
7981
}

normalized-cache-sqlite/src/appleMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheFactory.apple.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import app.cash.sqldelight.db.SqlDriver
55
import app.cash.sqldelight.driver.native.NativeSqliteDriver
66
import app.cash.sqldelight.driver.native.wrapConnection
77
import co.touchlab.sqliter.DatabaseConfiguration
8+
import com.apollographql.cache.normalized.api.NormalizedCache
89
import com.apollographql.cache.normalized.api.NormalizedCacheFactory
10+
import com.apollographql.cache.normalized.sql.internal.RecordDatabase
911
import com.apollographql.cache.normalized.sql.internal.record.SqlRecordDatabase
1012

1113
/**
@@ -17,7 +19,11 @@ import com.apollographql.cache.normalized.sql.internal.record.SqlRecordDatabase
1719
fun SqlNormalizedCacheFactory(
1820
name: String?,
1921
baseDir: String?,
20-
): NormalizedCacheFactory = SqlNormalizedCacheFactory(createDriver(name, baseDir), name)
22+
): NormalizedCacheFactory = object : NormalizedCacheFactory() {
23+
override fun create(): NormalizedCache {
24+
return SqlNormalizedCache(RecordDatabase(createDriver(name, baseDir), name))
25+
}
26+
}
2127

2228
actual fun SqlNormalizedCacheFactory(name: String?): NormalizedCacheFactory = SqlNormalizedCacheFactory(name, null)
2329

normalized-cache-sqlite/src/jsCommonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheFactory.jsCommon.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.apollographql.cache.normalized.sql
22

33
import app.cash.sqldelight.driver.worker.createDefaultWebWorkerDriver
4+
import com.apollographql.cache.normalized.api.NormalizedCache
45
import com.apollographql.cache.normalized.api.NormalizedCacheFactory
6+
import com.apollographql.cache.normalized.sql.internal.RecordDatabase
57

68
/**
79
* Returns a SqlNormalizedCacheFactory configured with the default driver which works with SQL.js.
@@ -14,7 +16,9 @@ import com.apollographql.cache.normalized.api.NormalizedCacheFactory
1416
*
1517
* See [the SQLDelight documentation](https://sqldelight.github.io/sqldelight/2.1.0/js_sqlite/sqljs_worker/).
1618
*/
17-
actual fun SqlNormalizedCacheFactory(name: String?): NormalizedCacheFactory {
18-
return SqlNormalizedCacheFactory(createDefaultWebWorkerDriver(), name = null)
19+
actual fun SqlNormalizedCacheFactory(name: String?): NormalizedCacheFactory = object : NormalizedCacheFactory() {
20+
override fun create(): NormalizedCache {
21+
return SqlNormalizedCache(RecordDatabase(createDefaultWebWorkerDriver(), name = null))
22+
}
1923
}
2024

normalized-cache-sqlite/src/jvmMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCacheFactory.jvm.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.apollographql.cache.normalized.sql
22

33
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
4+
import com.apollographql.cache.normalized.api.NormalizedCache
45
import com.apollographql.cache.normalized.api.NormalizedCacheFactory
6+
import com.apollographql.cache.normalized.sql.internal.RecordDatabase
57
import java.io.File
68
import java.util.Properties
79

@@ -13,7 +15,11 @@ import java.util.Properties
1315
fun SqlNormalizedCacheFactory(
1416
url: String,
1517
properties: Properties,
16-
): NormalizedCacheFactory = SqlNormalizedCacheFactory(JdbcSqliteDriver(url, properties), name = null)
18+
): NormalizedCacheFactory = object : NormalizedCacheFactory() {
19+
override fun create(): NormalizedCache {
20+
return SqlNormalizedCache(RecordDatabase(JdbcSqliteDriver(url, properties), name = null))
21+
}
22+
}
1723

1824
/**
1925
* @param name the name of the database or null for an in-memory database
@@ -24,7 +30,11 @@ fun SqlNormalizedCacheFactory(
2430
fun SqlNormalizedCacheFactory(
2531
name: String?,
2632
baseDir: String?,
27-
): NormalizedCacheFactory = SqlNormalizedCacheFactory(JdbcSqliteDriver(name.toUrl(baseDir), Properties()), name = name)
33+
): NormalizedCacheFactory = object : NormalizedCacheFactory() {
34+
override fun create(): NormalizedCache {
35+
return SqlNormalizedCache(RecordDatabase(JdbcSqliteDriver(name.toUrl(baseDir), Properties()), name = name))
36+
}
37+
}
2838

2939
actual fun SqlNormalizedCacheFactory(name: String?): NormalizedCacheFactory = SqlNormalizedCacheFactory(name, null)
3040

normalized-cache-sqlite/src/jvmTest/kotlin/com/apollographql/cache/normalized/sql/JvmSqlNormalizedCacheTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,30 @@ import com.apollographql.cache.normalized.api.DefaultRecordMerger
1111
import com.apollographql.cache.normalized.api.Record
1212
import com.apollographql.cache.normalized.sql.internal.RecordDatabase
1313
import com.apollographql.cache.normalized.testing.runTest
14+
import java.io.File
1415
import java.util.Properties
1516
import kotlin.test.Test
1617
import kotlin.test.assertEquals
18+
import kotlin.test.assertFalse
19+
import kotlin.test.assertTrue
1720

1821
class JvmSqlNormalizedCacheTest {
22+
@Test
23+
fun theDatabaseIsNotCreatedUntilTheCacheIs() {
24+
val baseDir = File(System.getProperty("java.io.tmpdir"), "apollo-test-${System.nanoTime()}")
25+
try {
26+
val factory = SqlNormalizedCacheFactory(name = "test.db", baseDir = baseDir.path)
27+
28+
// Creating the factory must not touch the disk.
29+
assertFalse(baseDir.exists(), "The base directory must not be created before NormalizedCacheFactory.create()")
30+
31+
factory.create()
32+
assertTrue(baseDir.exists(), "The base directory must be created by NormalizedCacheFactory.create()")
33+
} finally {
34+
baseDir.deleteRecursively()
35+
}
36+
}
37+
1938
@Test
2039
fun mergingIdenticalRecordIsANoOp() = runTest {
2140
val driver = SpyDriver(JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY, Properties()))

normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/DefaultCacheManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ internal class DefaultCacheManager(
141141
customScalarAdapters: CustomScalarAdapters,
142142
cacheHeaders: CacheHeaders,
143143
): ApolloResponse<D> {
144-
val cacheMissesAsException = cacheHeaders.headerValue(ApolloCacheHeaders.CACHE_MISSES_AS_EXCEPTION) == "true"
145-
val serverErrorsAsException = cacheHeaders.headerValue(ApolloCacheHeaders.SERVER_ERRORS_AS_EXCEPTION) == "true"
144+
val cacheMissesAsException = (cacheHeaders.headerValue(ApolloCacheHeaders.CACHE_MISSES_AS_EXCEPTION) ?: "true") == "true"
145+
val serverErrorsAsException = (cacheHeaders.headerValue(ApolloCacheHeaders.SERVER_ERRORS_AS_EXCEPTION) ?: "true") == "true"
146146
val variables = operation.variables(customScalarAdapters, true)
147147
val batchReaderData =
148148
try {

normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/options/cacheHeaders.kt

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,20 @@ internal class CacheHeadersContext(val value: CacheHeaders) : ExecutionContext.E
1515
override val key: ExecutionContext.Key<*>
1616
get() = Key
1717

18+
/**
19+
* Merge the [CacheHeaders] instead of letting the right element replace the right element.
20+
*/
21+
override fun <R> fold(initial: R, operation: (R, ExecutionContext.Element) -> R): R {
22+
val existing = (initial as? ExecutionContext)?.get(Key)
23+
val element = if (existing != null) CacheHeadersContext(existing.value + value) else this
24+
return operation(initial, element)
25+
}
26+
1827
companion object Key : ExecutionContext.Key<CacheHeadersContext>
1928
}
2029

2130
internal val ExecutionOptions.cacheHeaders: CacheHeaders
22-
get() = (executionContext[CacheHeadersContext]?.value ?: CacheHeaders.NONE).withDefaultValues
23-
24-
private val CacheHeaders.withDefaultValues: CacheHeaders
25-
get() = newBuilder()
26-
.apply {
27-
// Apply values for ExecutionOptions.cacheMissesAsException and ExecutionOptions.serverErrorsAsException
28-
// which are true by default contrary to other flags.
29-
if (!hasHeader(ApolloCacheHeaders.CACHE_MISSES_AS_EXCEPTION)) {
30-
addHeader(ApolloCacheHeaders.CACHE_MISSES_AS_EXCEPTION, "true")
31-
}
32-
if (!hasHeader(ApolloCacheHeaders.SERVER_ERRORS_AS_EXCEPTION)) {
33-
addHeader(ApolloCacheHeaders.SERVER_ERRORS_AS_EXCEPTION, "true")
34-
}
35-
}
36-
.build()
31+
get() = (executionContext[CacheHeadersContext]?.value ?: CacheHeaders.NONE)
3732

3833
fun <D : Operation.Data> ApolloResponse.Builder<D>.cacheHeaders(cacheHeaders: CacheHeaders) =
3934
addExecutionContext(CacheHeadersContext(cacheHeaders))
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.apollographql.cache.normalized.testing
2+
3+
import com.apollographql.cache.normalized.api.ApolloCacheHeaders
4+
import com.apollographql.cache.normalized.api.CacheHeaders
5+
6+
val noExceptionsHeaders = CacheHeaders.Builder()
7+
.addHeader(ApolloCacheHeaders.CACHE_MISSES_AS_EXCEPTION, "false")
8+
.addHeader(ApolloCacheHeaders.SERVER_ERRORS_AS_EXCEPTION, "false")
9+
.build()

0 commit comments

Comments
 (0)