From b49fe2c325337cc7d9a1f92af309071a19adf9b3 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Thu, 8 Jan 2026 18:11:19 -0600 Subject: [PATCH 1/4] Fetch current record before sharing. --- .../SQLiteData/CloudKit/CloudKitSharing.swift | 25 +++++++++++-------- .../CloudKit/Internal/MockCloudDatabase.swift | 12 +++++++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/CloudKitSharing.swift b/Sources/SQLiteData/CloudKit/CloudKitSharing.swift index 09f4181b..7254ab88 100644 --- a/Sources/SQLiteData/CloudKit/CloudKitSharing.swift +++ b/Sources/SQLiteData/CloudKit/CloudKitSharing.swift @@ -118,24 +118,27 @@ ) } let recordName = record.recordName - let lastKnownServerRecord = - try await metadatabase.read { db in + let lastKnownServerRecord = try await { + let lastKnownServerRecord = try await metadatabase.read { db in try SyncMetadata .where { $0.recordName.eq(recordName) } .select(\._lastKnownServerRecordAllFields) .fetchOne(db) } ?? nil - guard let lastKnownServerRecord - else { - throw SharingError( - recordTableName: T.tableName, - recordPrimaryKey: record.primaryKey.rawIdentifier, - reason: .recordMetadataNotFound, - debugDescription: """ + guard let lastKnownServerRecord + else { + throw SharingError( + recordTableName: T.tableName, + recordPrimaryKey: record.primaryKey.rawIdentifier, + reason: .recordMetadataNotFound, + debugDescription: """ No sync metadata found for record. Has the record been saved to the database? """ - ) - } + ) + } + return try await container.database(for: lastKnownServerRecord.recordID) + .record(for: lastKnownServerRecord.recordID) + }() var existingShare: CKShare? { get async throws { diff --git a/Sources/SQLiteData/CloudKit/Internal/MockCloudDatabase.swift b/Sources/SQLiteData/CloudKit/Internal/MockCloudDatabase.swift index 21c3c8a0..0c2fa6a0 100644 --- a/Sources/SQLiteData/CloudKit/Internal/MockCloudDatabase.swift +++ b/Sources/SQLiteData/CloudKit/Internal/MockCloudDatabase.swift @@ -189,6 +189,18 @@ // TODO: This should merge copy's values to more accurately reflect reality storage[recordToSave.recordID.zoneID]?.records[recordToSave.recordID] = copy saveResults[recordToSave.recordID] = .success(copy) + + // NB: "Touch" parent records when saving a child: + if let parent = recordToSave.parent, + // If the parent isn't also being saved in this batch. + !recordsToSave.contains(where: { $0.recordID == parent.recordID }), + // And if the parent is in the database. + let parentRecord = storage[parent.recordID.zoneID]?.records[parent.recordID]?.copy() + as? CKRecord + { + parentRecord._recordChangeTag = UUID().uuidString + storage[parent.recordID.zoneID]?.records[parent.recordID] = parentRecord + } } switch (existingRecord, recordToSave._recordChangeTag) { From 5c422818b2e4eda19ccae49d94e5799101b62f08 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Thu, 8 Jan 2026 18:17:36 -0600 Subject: [PATCH 2/4] add an explicit test --- .../CloudKitTests/SharingTests.swift | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index f74d730f..db656470 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -847,6 +847,86 @@ } } + /* + * Create parent record and synchronize. + * Create child record and synchronize. + * Share parent record. + */ + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func createParentThenChildThenShare() 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) + + let reminder = Reminder(id: 1, title: "Groceries", remindersListID: 1) + try await userDatabase.userWrite { db in + try db.seed { reminder } + } + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + + let _ = try await syncEngine.share(record: remindersList, configure: { _ in }) + + assertQuery( + SyncMetadata.select { ($0.share, $0.userModificationTime) }, + database: syncEngine.metadatabase + ) { + """ + ┌────────────────────────────────────────────────────────────────────────┬───┐ + │ CKRecord( │ 0 │ + │ recordID: CKRecord.ID(share-1:remindersLists/zone/__defaultOwner__), │ │ + │ recordType: "cloudkit.share", │ │ + │ parent: nil, │ │ + │ share: nil │ │ + │ ) │ │ + ├────────────────────────────────────────────────────────────────────────┼───┤ + │ nil │ 0 │ + └────────────────────────────────────────────────────────────────────────┴───┘ + """ + } + + assertInlineSnapshot(of: container, as: .customDump) { + """ + MockCloudContainer( + privateCloudDatabase: MockCloudDatabase( + databaseScope: .private, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(share-1:remindersLists/zone/__defaultOwner__), + recordType: "cloudkit.share", + parent: nil, + share: nil + ), + [1]: CKRecord( + recordID: CKRecord.ID(1:reminders/zone/__defaultOwner__), + recordType: "reminders", + parent: CKReference(recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__)), + share: nil, + id: 1, + isCompleted: 0, + remindersListID: 1, + title: "Groceries" + ), + [2]: CKRecord( + recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), + recordType: "remindersLists", + parent: nil, + share: CKReference(recordID: CKRecord.ID(share-1:remindersLists/zone/__defaultOwner__)), + id: 1, + title: "Personal" + ) + ] + ), + sharedCloudDatabase: MockCloudDatabase( + databaseScope: .shared, + storage: [] + ) + ) + """ + } + } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func shareTwice() async throws { let remindersList = RemindersList(id: 1, title: "Personal") From 439cd82a6a254f8457e932ff800cbc5bc31f83bd Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Thu, 8 Jan 2026 18:17:55 -0600 Subject: [PATCH 3/4] wip --- Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index db656470..9ed524fa 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -851,6 +851,7 @@ * Create parent record and synchronize. * Create child record and synchronize. * Share parent record. + * See: https://github.com/pointfreeco/sqlite-data/pull/363 */ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func createParentThenChildThenShare() async throws { From 47ed4364ce9ff40e457c41cfefc7c75b79d09271 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 13 Jan 2026 07:51:56 -0600 Subject: [PATCH 4/4] Move bug url to bug trait. --- Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index 9ed524fa..225f544b 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -851,10 +851,10 @@ * Create parent record and synchronize. * Create child record and synchronize. * Share parent record. - * See: https://github.com/pointfreeco/sqlite-data/pull/363 */ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @Test func createParentThenChildThenShare() async throws { + @Test(.bug("https://github.com/pointfreeco/sqlite-data/pull/363")) + func createParentThenChildThenShare() async throws { let remindersList = RemindersList(id: 1, title: "Personal") try await userDatabase.userWrite { db in try db.seed { remindersList }