diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 8edf5bac..878e832f 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -2045,11 +2045,11 @@ if data == nil { reportIssue("Asset data not found on disk") } - return "\(quote: columnName) = \(data?.queryFragment ?? "NULL")" + return "\(quote: columnName) = \(data?.queryFragment ?? #""excluded".\#(quote: columnName)"#)" } else { return """ \(quote: columnName) = \ - \(record.encryptedValues[columnName]?.queryFragment ?? "NULL") + \(record.encryptedValues[columnName]?.queryFragment ?? #""excluded".\#(quote: columnName)"#) """ } } diff --git a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift index f28d37b3..735d3cb4 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift @@ -196,6 +196,48 @@ } } + /* + * Old schema creates record and synchronizes to iCloud. + * Schema is migrated to add a "NOT NULL" column. + * New sync engine is launched. + => Sync starts without emitting an error. + */ + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func addColumn_OldRecordsSyncToNewSchema() async throws { + let remindersList = RemindersList(id: 1, title: "Personal") + try await userDatabase.userWrite { db in + try db.seed { + remindersList + } + } + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + + syncEngine.stop() + + try await userDatabase.userWrite { db in + try #sql( + """ + ALTER TABLE "remindersLists" + ADD COLUMN "position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0 + """ + ) + .execute(db) + } + + // NB: Sync engine should start without emitting issue. + _ = try await SyncEngine( + container: syncEngine.container, + userDatabase: syncEngine.userDatabase, + tables: syncEngine.tables + .filter { $0.base != Reminder.self && $0.base != RemindersList.self } + + [ + SynchronizedTable(for: ReminderWithPosition.self), + SynchronizedTable(for: RemindersListWithPosition.self), + ], + privateTables: syncEngine.privateTables + ) + } + /* * Test run from perspective of old device with old schema. * Old schema saves record in cloud database.