|
218 | 218 | try #sql( |
219 | 219 | """ |
220 | 220 | ALTER TABLE "remindersLists" |
221 | | - ADD COLUMN "position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0 |
| 221 | + ADD COLUMN "position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 42 |
222 | 222 | """ |
223 | 223 | ) |
224 | 224 | .execute(db) |
|
236 | 236 | ], |
237 | 237 | privateTables: syncEngine.privateTables |
238 | 238 | ) |
| 239 | + |
| 240 | + try await userDatabase.read { db in |
| 241 | + try #expect( |
| 242 | + RemindersListWithPosition.fetchAll(db) == [ |
| 243 | + RemindersListWithPosition(id: 1, title: "Personal", position: 42) |
| 244 | + ] |
| 245 | + ) |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | + /* |
| 250 | + * Old schema creates record and synchronizes to iCloud. |
| 251 | + * Schema is migrated to add a "NULL DEFAULT _" column. |
| 252 | + * New sync engine is launched. |
| 253 | + => Sync starts without emitting an error and default value is persisted in local database. |
| 254 | + */ |
| 255 | + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) |
| 256 | + @Test func addNullableColumn_OldRecordsSyncToNewSchema() async throws { |
| 257 | + let remindersList = RemindersList(id: 1, title: "Personal") |
| 258 | + try await userDatabase.userWrite { db in |
| 259 | + try db.seed { |
| 260 | + remindersList |
| 261 | + } |
| 262 | + } |
| 263 | + try await syncEngine.processPendingRecordZoneChanges(scope: .private) |
| 264 | + |
| 265 | + syncEngine.stop() |
| 266 | + |
| 267 | + try await userDatabase.userWrite { db in |
| 268 | + try #sql( |
| 269 | + """ |
| 270 | + ALTER TABLE "remindersLists" |
| 271 | + ADD COLUMN "color" INTEGER DEFAULT 42 |
| 272 | + """ |
| 273 | + ) |
| 274 | + .execute(db) |
| 275 | + } |
| 276 | + |
| 277 | + // NB: Sync engine should start without emitting issue. |
| 278 | + _ = try await SyncEngine( |
| 279 | + container: syncEngine.container, |
| 280 | + userDatabase: syncEngine.userDatabase, |
| 281 | + tables: syncEngine.tables |
| 282 | + .filter { $0.base != RemindersList.self } |
| 283 | + + [ |
| 284 | + SynchronizedTable(for: RemindersListWithColor.self), |
| 285 | + ], |
| 286 | + privateTables: syncEngine.privateTables |
| 287 | + ) |
| 288 | + |
| 289 | + try await userDatabase.read { db in |
| 290 | + try #expect( |
| 291 | + RemindersListWithColor.fetchAll(db) == [ |
| 292 | + RemindersListWithColor(id: 1, title: "Personal", color: 42) |
| 293 | + ] |
| 294 | + ) |
| 295 | + } |
239 | 296 | } |
240 | 297 |
|
241 | 298 | /* |
|
668 | 725 | ) |
669 | 726 | } |
670 | 727 | } |
| 728 | + |
| 729 | + /* |
| 730 | + * Sync engine saves records with old schema. |
| 731 | + * Sync engine is relaunched with new schema, adding a 'NOT NULL DEFAULT' column. |
| 732 | + => Defaults are set in local records. |
| 733 | + */ |
| 734 | + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) |
| 735 | + @Test func addNotNullColumn() async throws { |
| 736 | + } |
671 | 737 | } |
672 | 738 | } |
673 | 739 |
|
| 740 | +@Table("remindersLists") |
| 741 | +private struct RemindersListWithPosition: Equatable, Identifiable { |
| 742 | + let id: Int |
| 743 | + var title = "" |
| 744 | + var position = 0 |
| 745 | +} |
| 746 | + |
674 | 747 | @Table("remindersLists") |
675 | | - private struct RemindersListWithPosition: Equatable, Identifiable { |
| 748 | + private struct RemindersListWithColor: Equatable, Identifiable { |
676 | 749 | let id: Int |
677 | 750 | var title = "" |
678 | | - var position = 0 |
| 751 | + var color: Int? |
679 | 752 | } |
680 | 753 |
|
681 | 754 | @Table("reminders") |
|
0 commit comments