From 64837029cbefa73c039c866a596df1c33db65702 Mon Sep 17 00:00:00 2001 From: Matthaus Woolard Date: Wed, 7 Jan 2026 16:11:36 +1300 Subject: [PATCH] Add delegate method --- Sources/SQLiteData/CloudKit/SyncEngine.swift | 22 ++++++++++- .../CloudKit/SyncEngineDelegate.swift | 39 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 5d094197..ae629edd 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -1908,7 +1908,27 @@ do { try $_currentZoneID.withValue(serverRecord.recordID.zoneID) { - try #sql(upsert(table, record: serverRecord, columnNames: columnNames)).execute(db) + do { + try #sql( + upsert( + table, + record: serverRecord, + columnNames: columnNames + ) + ).execute(db) + } catch (let error as DatabaseError) { + if let delegate { + try delegate.handleUpsertFromServerRecord( + error: error, + serverRecord: serverRecord, + columnNames: columnNames, + table: T.self, + into: db + ) + } else { + throw error + } + } } try UnsyncedRecordID.find(serverRecord.recordID).delete().execute(db) try SyncMetadata diff --git a/Sources/SQLiteData/CloudKit/SyncEngineDelegate.swift b/Sources/SQLiteData/CloudKit/SyncEngineDelegate.swift index b373dfc4..17e5f131 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngineDelegate.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngineDelegate.swift @@ -78,6 +78,35 @@ _ syncEngine: SyncEngine, accountChanged changeType: CKSyncEngine.Event.AccountChange.ChangeType ) async + + + /// Handle any DatabaseError thrown while attempting to update or insert a record from iCloud + /// + /// By default, a sync engine will re-throw this error and end up not syncing the record. To override this behaviour, _e.g._ if your local schema is no longer compatible and you need to apply a custom data migration before inserting, implement this method, and explicitly handle your db updates. + /// + /// For example, your `PostalAddress` Table may have in the past used an enum field + /// for addressType: (residential, commercial etc) but now uses a foreignKey to a + /// dedicated table of `AddressType`. However the record being restored from iCloud + /// predates this database migration so it attempting to save the string `residential` + /// into the `addressType` field rather than the `ID` of the `AddressType` row who's + /// name is `residential`, overriding this method and handling update/inserts to `PostalAddress` + /// in this case will allow users to restore historic iCloud data when they re-install + /// the app even through the schema is no longer backwards compatible: + /// .... EXAMPLE here + /// + /// Parameters: + /// - error: The DatabaseError that was thrown. + /// - serverRecord: The CKRecord that is being updated or inserted. + /// - columnNames: The column names that contain an update. + /// - table: The local database table. + /// - Database: The database into which the changes should be written. + func handleUpsertFromServerRecord( + error: DatabaseError, + serverRecord: CKRecord, + columnNames: some Collection, + table: T.Type, + into database: Database + ) throws } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @@ -97,5 +126,15 @@ break } } + + public func handleUpsertFromServerRecord( + error: DatabaseError, + serverRecord: CKRecord, + columnNames: some Collection, + table: T.Type, + into database: Database + ) throws { + throw error + } } #endif