Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Sources/SQLiteData/CloudKit/CloudKit+StructuredQueries.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
if isTesting {
queryOutput._recordChangeTag =
coder
.decodeObject(of: NSNumber.self, forKey: "_recordChangeTag")?.intValue
.decodeObject(of: NSString.self, forKey: "_recordChangeTag") as String?
queryOutput._modificationDate =
coder
.decodeObject(of: NSDate.self, forKey: "_modificationDate") as Date?
Expand Down Expand Up @@ -111,7 +111,7 @@
if isTesting {
queryOutput._recordChangeTag =
coder
.decodeObject(of: NSNumber.self, forKey: "_recordChangeTag")?.intValue
.decodeObject(of: NSString.self, forKey: "_recordChangeTag") as String?
queryOutput._modificationDate =
coder
.decodeObject(of: NSDate.self, forKey: "_modificationDate") as Date?
Expand Down Expand Up @@ -397,7 +397,7 @@
private struct Unbindable: Error {}

extension CKRecord {
package var _recordChangeTag: Int? {
package var _recordChangeTag: String? {
get { self[#function] }
set { self[#function] = newValue }
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/SQLiteData/CloudKit/Internal/MockCloudDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
let clock: AnyClock<Duration>

package struct State {
private var lastRecordChangeTag = 0
private var changeTagCounter = 0
package var storage: [CKRecordZone.ID: Zone] = [:]
var assets: [AssetID: Data] = [:]
var deletedRecords: [(CKRecord.ID, CKRecord.RecordType)] = []
Expand All @@ -24,9 +24,9 @@
package var rateLimitDuration: Duration
fileprivate var requestTimestamps: [AnyClock<Duration>.Instant] = []
package var fetchedRecordIDs: [CKRecord.ID] = []
mutating func nextRecordChangeTag() -> Int {
lastRecordChangeTag += 1
return lastRecordChangeTag
mutating func nextRecordChangeTag() -> String {
defer { changeTagCounter += 1 }
return String(changeTagCounter, radix: 36)
}
init(
quota: Int = .max,
Expand Down
28 changes: 20 additions & 8 deletions Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,18 @@
}

modifications = state.withState { syncEngineState in
let currentChangeTag = syncEngineState.changeTag
let currentChangeTag = syncEngineState.currentChangeTag
return database.state.withValue { dbState in
zoneIDs.reduce(into: [CKRecord]()) { accum, zoneID in
accum += ((dbState.storage[zoneID]?.records.values).map { Array($0) } ?? [])
.map { $0.copy() as! CKRecord }
.filter {
precondition(
$0._recordChangeTag != nil,
"Records stored in database should have their 'recordChangeTag' assigned."
)
return $0._recordChangeTag! > currentChangeTag
guard let changeTag = $0._recordChangeTag else {
fatalError(
"Records stored in database should have their 'recordChangeTag' assigned."
)
}
return changeTag.isNewerChangeTag(than: currentChangeTag)
}
}
}
Expand All @@ -75,7 +76,11 @@
else { return }

state.withState {
$0.changeTag = modifications.compactMap(\._recordChangeTag).max() ?? $0.changeTag
for changeTag in modifications.compactMap(\._recordChangeTag) {
if changeTag.isNewerChangeTag(than: $0.currentChangeTag) {
$0.currentChangeTag = changeTag
}
}
}

await parentSyncEngine.handleEvent(
Expand Down Expand Up @@ -134,7 +139,7 @@
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
package final class MockSyncEngineState: CKSyncEngineStateProtocol {
package struct State: Sendable {
package var changeTag = 0
package var currentChangeTag: String?
package var hasPendingUntrackedChanges = false
package var pendingDatabaseChanges:
OrderedDictionary<CKRecordZone.ID, CKSyncEngine.PendingDatabaseChange> = [:]
Expand Down Expand Up @@ -513,4 +518,11 @@
}
}
}

extension String {
package func isNewerChangeTag(than other: String?) -> Bool {
guard let other else { return true }
return self.count != other.count ? self.count > other.count : self > other
}
}
#endif
2 changes: 1 addition & 1 deletion Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@
let record = try syncEngine.private.database.record(for: RemindersList.recordID(for: 1))
record.setValue("Work", forKey: "title", at: now)
// NB: Manually setting '_recordChangeTag' simulates another device saving a record.
record._recordChangeTag = .random(in: 9999 ... .max)
record._recordChangeTag = "zz"
try await syncEngine.modifyRecords(scope: .private, saving: [record]).notify()

assertQuery(Reminder.all, database: userDatabase.database) {
Expand Down
18 changes: 18 additions & 0 deletions Tests/SQLiteDataTests/CloudKitTests/MockSyncEngineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
#expect(!syncEngine.private.state.hasPendingUntrackedChanges)
}

@Test func isNewerChangeTag() {
#expect("0".isNewerChangeTag(than: nil))

#expect(!"0".isNewerChangeTag(than: "0"))
#expect(!"z".isNewerChangeTag(than: "z"))

#expect("1".isNewerChangeTag(than: "0"))
#expect(!"0".isNewerChangeTag(than: "1"))
#expect("a".isNewerChangeTag(than: "9"))
#expect(!"9".isNewerChangeTag(than: "a"))
#expect("z".isNewerChangeTag(than: "a"))

#expect("10".isNewerChangeTag(than: "z"))
#expect(!"z".isNewerChangeTag(than: "10"))
#expect("100".isNewerChangeTag(than: "zz"))
#expect(!"zz".isNewerChangeTag(than: "100"))
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@
recordType: "remindersLists",
parent: nil,
share: nil,
recordChangeTag: 3,
recordChangeTag: "2",
id: 1,
id🗓️: 0,
title: "Personal 3",
Expand Down
Loading