Skip to content
Closed
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
22 changes: 21 additions & 1 deletion Sources/SQLiteData/CloudKit/SyncEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 39 additions & 0 deletions Sources/SQLiteData/CloudKit/SyncEngineDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Table>(
error: DatabaseError,
serverRecord: CKRecord,
columnNames: some Collection<String>,
table: T.Type,
into database: Database
) throws
}

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
Expand All @@ -97,5 +126,15 @@
break
}
}

public func handleUpsertFromServerRecord<T: Table>(
error: DatabaseError,
serverRecord: CKRecord,
columnNames: some Collection<String>,
table: T.Type,
into database: Database
) throws {
throw error
}
}
#endif