Skip to content

Commit bdd291d

Browse files
committed
Fix CloudKit data loss on delete-then-reinsert
Fixes #418
1 parent 65502ac commit bdd291d

6 files changed

Lines changed: 553 additions & 17 deletions

File tree

Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,15 @@
173173
}
174174

175175
package func add(pendingRecordZoneChanges: [CKSyncEngine.PendingRecordZoneChange]) {
176-
self._pendingRecordZoneChanges.withValue {
177-
$0.append(contentsOf: pendingRecordZoneChanges)
176+
self._pendingRecordZoneChanges.withValue { set in
177+
for change in pendingRecordZoneChanges {
178+
if let id = change.id,
179+
let supersededIndex = set.firstIndex(where: { $0.id == id && $0 != change })
180+
{
181+
set.remove(at: supersededIndex)
182+
}
183+
set.append(change)
184+
}
178185
}
179186
}
180187

@@ -185,8 +192,15 @@
185192
}
186193

187194
package func add(pendingDatabaseChanges: [CKSyncEngine.PendingDatabaseChange]) {
188-
self._pendingDatabaseChanges.withValue {
189-
$0.append(contentsOf: pendingDatabaseChanges)
195+
self._pendingDatabaseChanges.withValue { set in
196+
for change in pendingDatabaseChanges {
197+
if let zoneID = change.zoneID,
198+
let supersededIndex = set.firstIndex(where: { $0.zoneID == zoneID && $0 != change })
199+
{
200+
set.remove(at: supersededIndex)
201+
}
202+
set.append(change)
203+
}
190204
}
191205
}
192206

Sources/SQLiteData/CloudKit/Internal/Triggers.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@
8282
defaultZone: defaultZone,
8383
privateTables: privateTables
8484
)
85+
SyncMetadata
86+
.where {
87+
$0.recordPrimaryKey.eq(#sql("\(new.primaryKey)"))
88+
&& $0.recordType.eq(tableName)
89+
&& $0._isDeleted
90+
}
91+
.update {
92+
$0._isDeleted = false
93+
$0.userModificationTime = $currentTime()
94+
}
8595
}
8696
)
8797
}
@@ -242,6 +252,7 @@
242252
afterZoneUpdateTrigger(),
243253
afterUpdateTrigger(for: syncEngine),
244254
afterSoftDeleteTrigger(for: syncEngine),
255+
afterUndeleteTrigger(for: syncEngine),
245256
]
246257
}
247258

@@ -348,6 +359,29 @@
348359
}
349360
)
350361
}
362+
363+
fileprivate static func afterUndeleteTrigger(
364+
for syncEngine: SyncEngine
365+
) -> TemporaryTrigger<Self> {
366+
createTemporaryTrigger(
367+
"\(String.sqliteDataCloudKitSchemaName)_after_undelete_on_sqlitedata_icloud_metadata",
368+
ifNotExists: true,
369+
after: .update(of: \._isDeleted) { _, new in
370+
Values(
371+
syncEngine.$didUpdate(
372+
recordName: new.recordName,
373+
zoneName: new.zoneName,
374+
ownerName: new.ownerName,
375+
oldZoneName: new.zoneName,
376+
oldOwnerName: new.ownerName,
377+
descendantRecordNames: #bind(nil)
378+
)
379+
)
380+
} when: { old, new in
381+
old._isDeleted && !new._isDeleted && !SyncEngine.$isSynchronizing
382+
}
383+
)
384+
}
351385
}
352386

353387
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)

Sources/SQLiteData/CloudKit/SyncEngine.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,6 +2092,20 @@
20922092
}
20932093
}
20942094

2095+
@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)
2096+
extension CKSyncEngine.PendingDatabaseChange {
2097+
var zoneID: CKRecordZone.ID? {
2098+
switch self {
2099+
case .saveZone(let zone):
2100+
return zone.zoneID
2101+
case .deleteZone(let zoneID):
2102+
return zoneID
2103+
@unknown default:
2104+
return nil
2105+
}
2106+
}
2107+
}
2108+
20952109
extension CKRecord.ID {
20962110
var tableName: String? {
20972111
guard

0 commit comments

Comments
 (0)