Skip to content

Commit d39ab5f

Browse files
committed
Ensure all fields get fresh timestamps on reinsertion
1 parent afc96c1 commit d39ab5f

4 files changed

Lines changed: 151 additions & 69 deletions

File tree

Sources/SQLiteData/CloudKit/Internal/Triggers.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
.update {
9292
$0._isDeleted = false
9393
$0.userModificationTime = $currentTime()
94+
$0._lastKnownServerRecordAllFields = #bind(nil)
9495
}
9596
}
9697
)

Sources/SQLiteData/CloudKit/SyncEngine.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,7 @@
11981198

11991199
let record =
12001200
allFields
1201+
?? metadata.lastKnownServerRecord
12011202
?? CKRecord(
12021203
recordType: metadata.recordType,
12031204
recordID: recordID

Tests/SQLiteDataTests/CloudKitTests/ChangeSupersessionTests.swift

Lines changed: 137 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -70,26 +70,66 @@
7070
"""
7171
}
7272
}
73-
73+
7474
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
75-
@Test func deleteAndReinsertInSingleWrite_saves() async throws {
75+
@Test func deleteThenReinsertThenDelete_deletes() async throws {
7676
try await userDatabase.userWrite { db in
77-
try db.seed {
78-
RemindersList(id: 1, title: "Original")
79-
}
77+
try db.seed { RemindersList(id: 1, title: "Original") }
8078
}
8179
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
8280

8381
try await userDatabase.userWrite { db in
8482
try RemindersList.find(1).delete().execute(db)
8583
try RemindersList.insert { RemindersList(id: 1, title: "Reinserted") }.execute(db)
84+
try RemindersList.find(1).delete().execute(db)
8685
}
8786

8887
let pending = syncEngine.private.state.pendingRecordZoneChanges
89-
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
88+
#expect(pending == [.deleteRecord(RemindersList.recordID(for: 1))])
89+
90+
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
91+
92+
assertInlineSnapshot(of: container, as: .customDump) {
93+
"""
94+
MockCloudContainer(
95+
privateCloudDatabase: MockCloudDatabase(
96+
databaseScope: .private,
97+
storage: []
98+
),
99+
sharedCloudDatabase: MockCloudDatabase(
100+
databaseScope: .shared,
101+
storage: []
102+
)
103+
)
104+
"""
105+
}
106+
}
90107

108+
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
109+
@Test(.printTimestamps) func deleteThenReinsertInSingleWrite_savesWithUpdatedTimestamps()
110+
async throws
111+
{
112+
try await userDatabase.userWrite { db in
113+
try db.seed {
114+
RemindersList(id: 1, title: "Original")
115+
}
116+
}
91117
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
92118

119+
try await withDependencies {
120+
$0.currentTime.now += 1
121+
} operation: {
122+
try await userDatabase.userWrite { db in
123+
try RemindersList.find(1).delete().execute(db)
124+
try RemindersList.insert { RemindersList(id: 1, title: "Reinserted") }.execute(db)
125+
}
126+
127+
let pending = syncEngine.private.state.pendingRecordZoneChanges
128+
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
129+
130+
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
131+
}
132+
93133
assertInlineSnapshot(of: container, as: .customDump) {
94134
"""
95135
MockCloudContainer(
@@ -102,7 +142,10 @@
102142
parent: nil,
103143
share: nil,
104144
id: 1,
105-
title: "Reinserted"
145+
id🗓️: 1,
146+
title: "Reinserted",
147+
title🗓️: 1,
148+
🗓️: 1
106149
)
107150
]
108151
),
@@ -116,25 +159,35 @@
116159
}
117160

118161
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
119-
@Test func deleteAndReinsertInSeparateWrites_saves() async throws {
162+
@Test(.printTimestamps) func deleteThenReinsertInSeparateWrites_savesWithUpdatedTimestamps()
163+
async throws
164+
{
120165
try await userDatabase.userWrite { db in
121166
try db.seed {
122167
RemindersList(id: 1, title: "Original")
123168
}
124169
}
125170
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
126171

127-
try await userDatabase.userWrite { db in
128-
try RemindersList.find(1).delete().execute(db)
129-
}
130-
try await userDatabase.userWrite { db in
131-
try RemindersList.insert { RemindersList(id: 1, title: "Reinserted") }.execute(db)
132-
}
172+
try await withDependencies {
173+
$0.currentTime.now += 1
174+
} operation: {
175+
try await userDatabase.userWrite { db in
176+
try RemindersList.find(1).delete().execute(db)
177+
}
178+
try await withDependencies {
179+
$0.currentTime.now += 1
180+
} operation: {
181+
try await userDatabase.userWrite { db in
182+
try RemindersList.insert { RemindersList(id: 1, title: "Reinserted") }.execute(db)
183+
}
133184

134-
let pending = syncEngine.private.state.pendingRecordZoneChanges
135-
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
185+
let pending = syncEngine.private.state.pendingRecordZoneChanges
186+
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
136187

137-
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
188+
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
189+
}
190+
}
138191

139192
assertInlineSnapshot(of: container, as: .customDump) {
140193
"""
@@ -148,7 +201,10 @@
148201
parent: nil,
149202
share: nil,
150203
id: 1,
151-
title: "Reinserted"
204+
id🗓️: 2,
205+
title: "Reinserted",
206+
title🗓️: 2,
207+
🗓️: 2
152208
)
153209
]
154210
),
@@ -162,22 +218,28 @@
162218
}
163219

164220
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
165-
@Test func updateThenDeleteThenReinsert_saves() async throws {
221+
@Test(.printTimestamps) func updateThenDeleteThenReinsert_savesWithUpdatedTimestamps()
222+
async throws
223+
{
166224
try await userDatabase.userWrite { db in
167225
try db.seed { RemindersList(id: 1, title: "Original") }
168226
}
169227
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
170228

171-
try await userDatabase.userWrite { db in
172-
try RemindersList.find(1).update { $0.title = "Updated" }.execute(db)
173-
try RemindersList.find(1).delete().execute(db)
174-
try RemindersList.insert { RemindersList(id: 1, title: "Reinserted") }.execute(db)
175-
}
229+
try await withDependencies {
230+
$0.currentTime.now += 1
231+
} operation: {
232+
try await userDatabase.userWrite { db in
233+
try RemindersList.find(1).update { $0.title = "Updated" }.execute(db)
234+
try RemindersList.find(1).delete().execute(db)
235+
try RemindersList.insert { RemindersList(id: 1, title: "Reinserted") }.execute(db)
236+
}
176237

177-
let pending = syncEngine.private.state.pendingRecordZoneChanges
178-
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
238+
let pending = syncEngine.private.state.pendingRecordZoneChanges
239+
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
179240

180-
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
241+
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
242+
}
181243

182244
assertInlineSnapshot(of: container, as: .customDump) {
183245
"""
@@ -191,7 +253,10 @@
191253
parent: nil,
192254
share: nil,
193255
id: 1,
194-
title: "Reinserted"
256+
id🗓️: 1,
257+
title: "Reinserted",
258+
title🗓️: 1,
259+
🗓️: 1
195260
)
196261
]
197262
),
@@ -203,31 +268,46 @@
203268
"""
204269
}
205270
}
206-
271+
207272
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
208-
@Test func deleteReinsertThenDeleteAgain_deletes() async throws {
273+
@Test(.printTimestamps) func deleteThenReinsertWithSameValue_savesWithUpdatedTimestamps()
274+
async throws
275+
{
209276
try await userDatabase.userWrite { db in
210277
try db.seed { RemindersList(id: 1, title: "Original") }
211278
}
212279
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
213280

214-
try await userDatabase.userWrite { db in
215-
try RemindersList.find(1).delete().execute(db)
216-
try RemindersList.insert { RemindersList(id: 1, title: "Reinserted") }.execute(db)
217-
try RemindersList.find(1).delete().execute(db)
281+
try await withDependencies {
282+
$0.currentTime.now += 1
283+
} operation: {
284+
try await userDatabase.userWrite { db in
285+
try RemindersList.find(1).delete().execute(db)
286+
try RemindersList.insert { RemindersList(id: 1, title: "Original") }.execute(db)
287+
}
288+
let pending = syncEngine.private.state.pendingRecordZoneChanges
289+
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
290+
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
218291
}
219292

220-
let pending = syncEngine.private.state.pendingRecordZoneChanges
221-
#expect(pending == [.deleteRecord(RemindersList.recordID(for: 1))])
222-
223-
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
224-
225293
assertInlineSnapshot(of: container, as: .customDump) {
226294
"""
227295
MockCloudContainer(
228296
privateCloudDatabase: MockCloudDatabase(
229297
databaseScope: .private,
230-
storage: []
298+
storage: [
299+
[0]: CKRecord(
300+
recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__),
301+
recordType: "remindersLists",
302+
parent: nil,
303+
share: nil,
304+
id: 1,
305+
id🗓️: 1,
306+
title: "Original",
307+
title🗓️: 1,
308+
🗓️: 1
309+
)
310+
]
231311
),
232312
sharedCloudDatabase: MockCloudDatabase(
233313
databaseScope: .shared,
@@ -239,7 +319,7 @@
239319
}
240320

241321
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
242-
@Test(.printTimestamps) func twoDeleteReinsertCyclesInSameWrite_propagatesLatestValueAndTimestamp()
322+
@Test(.printTimestamps) func twoDeleteReinsertCyclesInSameWrite_savesLatestWithUpdatedTimestamps()
243323
async throws
244324
{
245325
try await userDatabase.userWrite { db in
@@ -248,7 +328,7 @@
248328
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
249329

250330
try await withDependencies {
251-
$0.currentTime.now = 1
331+
$0.currentTime.now += 1
252332
} operation: {
253333
try await userDatabase.userWrite { db in
254334
try RemindersList.find(1).delete().execute(db)
@@ -273,7 +353,7 @@
273353
parent: nil,
274354
share: nil,
275355
id: 1,
276-
id🗓️: 0,
356+
id🗓️: 1,
277357
title: "Final",
278358
title🗓️: 1,
279359
🗓️: 1
@@ -290,7 +370,7 @@
290370
}
291371

292372
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
293-
@Test(.printTimestamps) func twoDeleteReinsertCyclesInSeparateBatches_propagatesLatestValueAndTimestamp()
373+
@Test(.printTimestamps) func twoDeleteReinsertCyclesInSeparateBatches_savesLatestWithUpdatedTimestamps()
294374
async throws
295375
{
296376
try await userDatabase.userWrite { db in
@@ -299,7 +379,7 @@
299379
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
300380

301381
try await withDependencies {
302-
$0.currentTime.now = 1
382+
$0.currentTime.now += 1
303383
} operation: {
304384
try await userDatabase.userWrite { db in
305385
try RemindersList.find(1).delete().execute(db)
@@ -308,18 +388,18 @@
308388
let pending = syncEngine.private.state.pendingRecordZoneChanges
309389
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
310390
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
311-
}
312-
313-
try await withDependencies {
314-
$0.currentTime.now = 2
315-
} operation: {
316-
try await userDatabase.userWrite { db in
317-
try RemindersList.find(1).delete().execute(db)
318-
try RemindersList.insert { RemindersList(id: 1, title: "Cycle2") }.execute(db)
391+
392+
try await withDependencies {
393+
$0.currentTime.now += 1
394+
} operation: {
395+
try await userDatabase.userWrite { db in
396+
try RemindersList.find(1).delete().execute(db)
397+
try RemindersList.insert { RemindersList(id: 1, title: "Cycle2") }.execute(db)
398+
}
399+
let pending = syncEngine.private.state.pendingRecordZoneChanges
400+
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
401+
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
319402
}
320-
let pending = syncEngine.private.state.pendingRecordZoneChanges
321-
#expect(pending == [.saveRecord(RemindersList.recordID(for: 1))])
322-
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
323403
}
324404

325405
assertInlineSnapshot(of: container, as: .customDump) {
@@ -334,7 +414,7 @@
334414
parent: nil,
335415
share: nil,
336416
id: 1,
337-
id🗓️: 0,
417+
id🗓️: 2,
338418
title: "Cycle2",
339419
title🗓️: 2,
340420
🗓️: 2

0 commit comments

Comments
 (0)