Skip to content

Commit a3049c7

Browse files
committed
Structurally enforce ID-based deduplication in MockSyncEngineState
1 parent bdd291d commit a3049c7

5 files changed

Lines changed: 47 additions & 42 deletions

File tree

Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,12 @@
136136
package final class MockSyncEngineState: CKSyncEngineStateProtocol {
137137
package let changeTag = LockIsolated(0)
138138
package let _pendingRecordZoneChanges = LockIsolated<
139-
OrderedSet<CKSyncEngine.PendingRecordZoneChange>
140-
>([]
139+
OrderedDictionary<CKRecord.ID, CKSyncEngine.PendingRecordZoneChange>
140+
>([:]
141141
)
142142
package let _pendingDatabaseChanges = LockIsolated<
143-
OrderedSet<CKSyncEngine.PendingDatabaseChange>
144-
>([])
143+
OrderedDictionary<CKRecordZone.ID, CKSyncEngine.PendingDatabaseChange>
144+
>([:])
145145
private let fileID: StaticString
146146
private let filePath: StaticString
147147
private let line: UInt
@@ -160,11 +160,11 @@
160160
}
161161

162162
package var pendingRecordZoneChanges: [CKSyncEngine.PendingRecordZoneChange] {
163-
_pendingRecordZoneChanges.withValue { Array($0) }
163+
_pendingRecordZoneChanges.withValue { Array($0.values) }
164164
}
165165

166166
package var pendingDatabaseChanges: [CKSyncEngine.PendingDatabaseChange] {
167-
_pendingDatabaseChanges.withValue { Array($0) }
167+
_pendingDatabaseChanges.withValue { Array($0.values) }
168168
}
169169

170170
package func removePendingChanges() {
@@ -173,40 +173,58 @@
173173
}
174174

175175
package func add(pendingRecordZoneChanges: [CKSyncEngine.PendingRecordZoneChange]) {
176-
self._pendingRecordZoneChanges.withValue { set in
176+
self._pendingRecordZoneChanges.withValue { dict in
177177
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)
178+
switch change {
179+
case .saveRecord(let id), .deleteRecord(let id):
180+
dict.updateValue(change, forKey: id)
181+
@unknown default:
182+
fatalError("Unsupported pendingRecordZoneChange: \(change)")
182183
}
183-
set.append(change)
184184
}
185185
}
186186
}
187187

188188
package func remove(pendingRecordZoneChanges: [CKSyncEngine.PendingRecordZoneChange]) {
189-
self._pendingRecordZoneChanges.withValue {
190-
$0.subtract(pendingRecordZoneChanges)
189+
self._pendingRecordZoneChanges.withValue { dict in
190+
for change in pendingRecordZoneChanges {
191+
switch change {
192+
case .saveRecord(let id), .deleteRecord(let id):
193+
if dict[id] == change { dict.removeValue(forKey: id) }
194+
@unknown default:
195+
fatalError("Unsupported pendingRecordZoneChange: \(change)")
196+
}
197+
}
191198
}
192199
}
193200

194201
package func add(pendingDatabaseChanges: [CKSyncEngine.PendingDatabaseChange]) {
195-
self._pendingDatabaseChanges.withValue { set in
202+
self._pendingDatabaseChanges.withValue { dict in
196203
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)
204+
switch change {
205+
case .saveZone(let zone):
206+
dict.updateValue(change, forKey: zone.zoneID)
207+
case .deleteZone(let zoneID):
208+
dict.updateValue(change, forKey: zoneID)
209+
@unknown default:
210+
fatalError("Unsupported pendingDatabaseChange: \(change)")
201211
}
202-
set.append(change)
203212
}
204213
}
205214
}
206215

207216
package func remove(pendingDatabaseChanges: [CKSyncEngine.PendingDatabaseChange]) {
208-
self._pendingDatabaseChanges.withValue {
209-
$0.subtract(pendingDatabaseChanges)
217+
self._pendingDatabaseChanges.withValue { dict in
218+
for change in pendingDatabaseChanges {
219+
switch change {
220+
case .saveZone(let zone):
221+
if dict[zone.zoneID] == change { dict.removeValue(forKey: zone.zoneID) }
222+
case .deleteZone(let zoneID):
223+
if dict[zoneID] == change { dict.removeValue(forKey: zoneID) }
224+
@unknown default:
225+
fatalError("Unsupported pendingDatabaseChange: \(change)")
226+
}
227+
}
210228
}
211229
}
212230
}

Sources/SQLiteData/CloudKit/SyncEngine.swift

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,20 +2092,6 @@
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-
21092095
extension CKRecord.ID {
21102096
var tableName: String? {
21112097
guard

Tests/SQLiteDataTests/CloudKitTests/MockSyncEngineStateTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
@Test func crossTypeSupersession_doesNotAffectOtherRecords() {
4848
state.add(pendingRecordZoneChanges: [.saveRecord(idA), .saveRecord(idB)])
4949
state.add(pendingRecordZoneChanges: [.deleteRecord(idA)])
50-
#expect(state.pendingRecordZoneChanges == [.saveRecord(idB), .deleteRecord(idA)])
50+
#expect(state.pendingRecordZoneChanges == [.deleteRecord(idA), .saveRecord(idB)])
5151
}
5252
}
5353

@@ -91,7 +91,7 @@
9191
@Test func crossTypeSupersession_doesNotAffectOtherZones() {
9292
state.add(pendingDatabaseChanges: [.saveZone(zoneA), .saveZone(zoneB)])
9393
state.add(pendingDatabaseChanges: [.deleteZone(zoneAID)])
94-
#expect(state.pendingDatabaseChanges == [.saveZone(zoneB), .deleteZone(zoneAID)])
94+
#expect(state.pendingDatabaseChanges == [.deleteZone(zoneAID), .saveZone(zoneB)])
9595
}
9696
}
9797
}

Tests/SQLiteDataTests/Internal/CloudKit+CustomDump.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#if canImport(CloudKit)
22
import CustomDump
33
import CloudKit
4+
import OrderedCollections
45
import SQLiteData
56

67
extension CKDatabase.Scope: @retroactive CustomDumpStringConvertible {
@@ -169,13 +170,13 @@
169170
children: [
170171
(
171172
"pendingRecordZoneChanges",
172-
_pendingRecordZoneChanges.withValue(\.self)
173+
_pendingRecordZoneChanges.withValue { Array($0.values) }
173174
.sorted(by: comparePendingRecordZoneChange)
174175
as Any
175176
),
176177
(
177178
"pendingDatabaseChanges",
178-
_pendingDatabaseChanges.withValue(\.self)
179+
_pendingDatabaseChanges.withValue { Array($0.values) }
179180
.sorted(by: comparePendingDatabaseChange) as Any
180181
),
181182
],

Tests/SQLiteDataTests/Internal/CloudKitTestHelpers.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ extension MockSyncEngineState {
164164
_pendingRecordZoneChanges.withValue {
165165
expectNoDifference(
166166
Set(changes),
167-
Set($0),
167+
Set($0.values),
168168
fileID: fileID,
169169
filePath: filePath,
170170
line: line,
@@ -184,7 +184,7 @@ extension MockSyncEngineState {
184184
_pendingDatabaseChanges.withValue {
185185
expectNoDifference(
186186
Set(changes),
187-
Set($0),
187+
Set($0.values),
188188
fileID: fileID,
189189
filePath: filePath,
190190
line: line,

0 commit comments

Comments
 (0)