Skip to content

Commit 0ade2d4

Browse files
committed
Avoid inserting identical records
1 parent ad5c514 commit 0ade2d4

5 files changed

Lines changed: 119 additions & 6 deletions

File tree

normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,11 @@ class SqlNormalizedCache internal constructor(
173173
val record = record.withDates(receivedDate = receivedDate, expirationDate = expirationDate)
174174
val existingRecord = existingRecords[record.key]
175175
if (existingRecord == null) {
176-
recordDatabase.insertOrUpdateRecord(record)
176+
recordDatabase.insertOrUpdateRecord(record, deleteFirst = false)
177177
record.fieldKeys()
178178
} else {
179179
val (mergedRecord, changedKeys) = recordMerger.merge(RecordMergerContext(existing = existingRecord, incoming = record, cacheHeaders = cacheHeaders))
180-
if (mergedRecord.isNotEmpty()) {
180+
if (mergedRecord.isNotEmpty() && mergedRecord != existingRecord) {
181181
recordDatabase.insertOrUpdateRecord(mergedRecord)
182182
}
183183
changedKeys

normalized-cache-sqlite/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/internal/RecordDatabase.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ internal class RecordDatabase(
100100
/**
101101
* Must be called inside a transaction.
102102
*/
103-
suspend fun insertOrUpdateRecord(record: Record) {
104-
recordQueries.deleteRecords(listOf(record.key.key))
103+
suspend fun insertOrUpdateRecord(record: Record, deleteFirst: Boolean = true) {
104+
if (deleteFirst) recordQueries.deleteRecords(listOf(record.key.key))
105105
val recordBytes = RecordSerializer.serialize(record)
106106
val updatedDate = currentTimeMillis()
107107
// Fast path for small records
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.apollographql.cache.normalized.sql
2+
3+
import app.cash.sqldelight.db.QueryResult
4+
import app.cash.sqldelight.db.SqlCursor
5+
import app.cash.sqldelight.db.SqlDriver
6+
import app.cash.sqldelight.db.SqlPreparedStatement
7+
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
8+
import com.apollographql.cache.normalized.api.CacheHeaders
9+
import com.apollographql.cache.normalized.api.CacheKey
10+
import com.apollographql.cache.normalized.api.DefaultRecordMerger
11+
import com.apollographql.cache.normalized.api.Record
12+
import com.apollographql.cache.normalized.sql.internal.RecordDatabase
13+
import com.apollographql.cache.normalized.testing.runTest
14+
import java.util.Properties
15+
import kotlin.test.Test
16+
import kotlin.test.assertEquals
17+
18+
class JvmSqlNormalizedCacheTest {
19+
@Test
20+
fun mergingIdenticalRecordIsANoOp() = runTest {
21+
val driver = SpyDriver(JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY, Properties()))
22+
val cache = SqlNormalizedCache(RecordDatabase(driver, null))
23+
// Initial insert
24+
cache.merge(
25+
record = Record(
26+
key = CacheKey.QUERY_ROOT,
27+
fields = mapOf(
28+
"a" to "0",
29+
),
30+
),
31+
cacheHeaders = CacheHeaders.NONE,
32+
recordMerger = DefaultRecordMerger,
33+
)
34+
var count = driver.count
35+
36+
// Insert the same record
37+
cache.merge(
38+
record = Record(
39+
key = CacheKey.QUERY_ROOT,
40+
fields = mapOf(
41+
"a" to "0",
42+
),
43+
),
44+
cacheHeaders = CacheHeaders.NONE,
45+
recordMerger = DefaultRecordMerger,
46+
)
47+
48+
// Expected 1 select, 0 insert
49+
assertEquals(1, driver.count - count)
50+
count = driver.count
51+
52+
// Insert a different record
53+
cache.merge(
54+
record = Record(
55+
key = CacheKey.QUERY_ROOT,
56+
fields = mapOf(
57+
"a" to "1",
58+
),
59+
),
60+
cacheHeaders = CacheHeaders.NONE,
61+
recordMerger = DefaultRecordMerger,
62+
)
63+
64+
// Expected 1 select, 1 delete, 1 insert
65+
assertEquals(3, driver.count - count)
66+
67+
}
68+
}
69+
70+
private class SpyDriver(private val delegate: SqlDriver) : SqlDriver by delegate {
71+
var count = 0
72+
73+
override fun execute(
74+
identifier: Int?,
75+
sql: String,
76+
parameters: Int,
77+
binders: (SqlPreparedStatement.() -> Unit)?,
78+
): QueryResult<Long> {
79+
count++
80+
return delegate.execute(identifier, sql, parameters, binders)
81+
}
82+
83+
override fun <R> executeQuery(
84+
identifier: Int?,
85+
sql: String,
86+
mapper: (SqlCursor) -> QueryResult<R>,
87+
parameters: Int,
88+
binders: (SqlPreparedStatement.() -> Unit)?,
89+
): QueryResult<R> {
90+
count++
91+
return delegate.executeQuery(identifier, sql, mapper, parameters, binders)
92+
}
93+
}

normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ class Record(
5757
return result
5858
}
5959

60+
override fun equals(other: Any?): Boolean {
61+
if (this === other) return true
62+
if (other !is Record) return false
63+
64+
if (key != other.key) return false
65+
if (fields != other.fields) return false
66+
if (metadata != other.metadata) return false
67+
68+
return true
69+
}
70+
71+
override fun hashCode(): Int {
72+
var result = key.hashCode()
73+
result = 31 * result + fields.hashCode()
74+
result = 31 * result + metadata.hashCode()
75+
return result
76+
}
77+
6078
companion object {
6179
internal fun changedKeys(record1: Record, record2: Record): Set<String> {
6280
check(record1.key == record2.key) {

normalized-cache/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,10 @@ class MemoryCache(
130130
record.fieldKeys()
131131
} else {
132132
val (mergedRecord, changedKeys) = recordMerger.merge(RecordMergerContext(existing = existingRecord, incoming = record, cacheHeaders = cacheHeaders))
133-
recordsToInsert.add(mergedRecord)
134-
lruCache[record.key] = mergedRecord
133+
if (mergedRecord != existingRecord) {
134+
recordsToInsert.add(mergedRecord)
135+
lruCache[record.key] = mergedRecord
136+
}
135137
changedKeys
136138
}
137139
}.toSet()

0 commit comments

Comments
 (0)