From 028539f26fc175090d34fe30f601200428e94948 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 24 Nov 2025 11:51:26 -0600 Subject: [PATCH] Fix logic that determines sharing private tables. --- .../xcshareddata/swiftpm/Package.resolved | 20 ++++++- Sources/SQLiteData/CloudKit/SyncEngine.swift | 3 +- .../CloudKitTests/AccountLifecycleTests.swift | 9 ++-- .../CloudKitTests/CloudKitTests.swift | 20 +++---- .../FetchedDatabaseChangesTests.swift | 14 +++-- .../NextRecordZoneChangeBatchTests.swift | 5 +- .../CloudKitTests/RecordTypeTests.swift | 17 ++---- .../CloudKitTests/SharingTests.swift | 54 ++++++++++++++++++- .../CloudKitTests/TriggerTests.swift | 16 +++--- Tests/SQLiteDataTests/Internal/Schema.swift | 11 ++-- 10 files changed, 110 insertions(+), 59 deletions(-) diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a0d54872..587d0c8d 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "c133bf7d10c8ce1e5d6506c3d2f080eac8b4c8c2827044d53a9b925e903564fd", + "originHash" : "41e7781e6c506773b6af84af513bcd6d3b1be59d635e6c4c4bd89638368e4629", "pins" : [ { "identity" : "combine-schedulers", @@ -73,6 +73,24 @@ "version" : "1.9.4" } }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06", + "version" : "1.4.5" + } + }, + { + "identity" : "swift-docc-symbolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-docc-symbolkit", + "state" : { + "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", + "version" : "1.0.0" + } + }, { "identity" : "swift-identified-collections", "kind" : "remoteSourceControl", diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 76bbbb71..c6f29d18 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -1127,8 +1127,7 @@ recordID: recordID ) if let parentRecordName = metadata.parentRecordName, - let parentRecordType = metadata.parentRecordType, - !privateTables.contains(where: { $0.base.tableName == parentRecordType }) + !privateTables.contains(where: { $0.base.tableName == metadata.recordType }) { record.parent = CKRecord.Reference( recordID: CKRecord.ID( diff --git a/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift index a9641ceb..c9e7c117 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift @@ -17,7 +17,7 @@ try db.seed { RemindersList(id: 1, title: "Personal") Reminder(id: 1, title: "Get milk", remindersListID: 1) - RemindersListPrivate(id: 1, remindersListID: 1) + RemindersListPrivate(remindersListID: 1) UnsyncedModel(id: 1) } } @@ -43,7 +43,7 @@ try db.seed { RemindersList(id: 1, title: "Personal") Reminder(id: 1, title: "Get milk", remindersListID: 1) - RemindersListPrivate(id: 1, remindersListID: 1) + RemindersListPrivate(remindersListID: 1) UnsyncedModel(id: 1) } } @@ -95,9 +95,8 @@ [1]: CKRecord( recordID: CKRecord.ID(1:remindersListPrivates/zone/__defaultOwner__), recordType: "remindersListPrivates", - parent: CKReference(recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__)), + parent: nil, share: nil, - id: 1, position: 0, remindersListID: 1 ), @@ -611,7 +610,7 @@ try db.seed { RemindersList(id: 1, title: "Personal") Reminder(id: 1, title: "Get milk", remindersListID: 1) - RemindersListPrivate(id: 1, remindersListID: 1) + RemindersListPrivate(remindersListID: 1) UnsyncedModel(id: 1) } } diff --git a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift index 169a1122..7b778081 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift @@ -75,29 +75,22 @@ tableName: "remindersListPrivates", schema: """ CREATE TABLE "remindersListPrivates" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0, - "remindersListID" INTEGER NOT NULL REFERENCES "remindersLists"("id") ON DELETE CASCADE + "remindersListID" INTEGER PRIMARY KEY NOT NULL REFERENCES "remindersLists"("id") + ON DELETE CASCADE, + "position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0 ) STRICT """, tableInfo: [ [0]: TableInfo( - defaultValue: nil, - isPrimaryKey: true, - name: "id", - isNotNull: true, - type: "INTEGER" - ), - [1]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "position", isNotNull: true, type: "INTEGER" ), - [2]: TableInfo( + [1]: TableInfo( defaultValue: nil, - isPrimaryKey: false, + isPrimaryKey: true, name: "remindersListID", isNotNull: true, type: "INTEGER" @@ -769,7 +762,7 @@ try await userDatabase.userWrite { db in try db.seed { RemindersList(id: 1, title: "Personal") - RemindersListPrivate(id: 1, position: 1, remindersListID: 1) + RemindersListPrivate(remindersListID: 1, position: 1) Reminder(id: 1, title: "", remindersListID: 1) Reminder(id: 2, title: "", remindersListID: 1) Reminder(id: 3, title: "", remindersListID: 1) @@ -901,6 +894,5 @@ } } } - } #endif diff --git a/Tests/SQLiteDataTests/CloudKitTests/FetchedDatabaseChangesTests.swift b/Tests/SQLiteDataTests/CloudKitTests/FetchedDatabaseChangesTests.swift index d73cb343..3b52db96 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/FetchedDatabaseChangesTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/FetchedDatabaseChangesTests.swift @@ -20,8 +20,8 @@ RemindersList(id: 2, title: "Business") Reminder(id: 1, title: "Get milk", remindersListID: 1) Reminder(id: 2, title: "Call accountant", remindersListID: 2) - RemindersListPrivate(id: 1, remindersListID: 1) - RemindersListPrivate(id: 2, remindersListID: 2) + RemindersListPrivate(remindersListID: 1) + RemindersListPrivate(remindersListID: 2) UnsyncedModel(id: 1) UnsyncedModel(id: 2) } @@ -52,8 +52,8 @@ RemindersList(id: 2, title: "Business") Reminder(id: 1, title: "Get milk", remindersListID: 1) Reminder(id: 2, title: "Call accountant", remindersListID: 2) - RemindersListPrivate(id: 1, remindersListID: 1) - RemindersListPrivate(id: 2, remindersListID: 2) + RemindersListPrivate(remindersListID: 1) + RemindersListPrivate(remindersListID: 2) UnsyncedModel(id: 1) UnsyncedModel(id: 2) } @@ -106,18 +106,16 @@ [2]: CKRecord( recordID: CKRecord.ID(1:remindersListPrivates/zone/__defaultOwner__), recordType: "remindersListPrivates", - parent: CKReference(recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__)), + parent: nil, share: nil, - id: 1, position: 0, remindersListID: 1 ), [3]: CKRecord( recordID: CKRecord.ID(2:remindersListPrivates/zone/__defaultOwner__), recordType: "remindersListPrivates", - parent: CKReference(recordID: CKRecord.ID(2:remindersLists/zone/__defaultOwner__)), + parent: nil, share: nil, - id: 2, position: 0, remindersListID: 2 ), diff --git a/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift b/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift index 2895ff0e..b36cbd8a 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift @@ -225,7 +225,7 @@ try await userDatabase.userWrite { db in try db.seed { RemindersList(id: 1, title: "Personal") - RemindersListPrivate(id: 1, position: 42, remindersListID: 1) + RemindersListPrivate(remindersListID: 1, position: 42) } } @@ -239,9 +239,8 @@ [0]: CKRecord( recordID: CKRecord.ID(1:remindersListPrivates/zone/__defaultOwner__), recordType: "remindersListPrivates", - parent: CKReference(recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__)), + parent: nil, share: nil, - id: 1, position: 42, remindersListID: 1 ), diff --git a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift index a863f612..e893bf3e 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift @@ -73,29 +73,22 @@ tableName: "remindersListPrivates", schema: """ CREATE TABLE "remindersListPrivates" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0, - "remindersListID" INTEGER NOT NULL REFERENCES "remindersLists"("id") ON DELETE CASCADE + "remindersListID" INTEGER PRIMARY KEY NOT NULL REFERENCES "remindersLists"("id") + ON DELETE CASCADE, + "position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0 ) STRICT """, tableInfo: [ [0]: TableInfo( - defaultValue: nil, - isPrimaryKey: true, - name: "id", - isNotNull: true, - type: "INTEGER" - ), - [1]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "position", isNotNull: true, type: "INTEGER" ), - [2]: TableInfo( + [1]: TableInfo( defaultValue: nil, - isPrimaryKey: false, + isPrimaryKey: true, name: "remindersListID", isNotNull: true, type: "INTEGER" diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index f6c556a1..ae3b71f4 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -116,7 +116,7 @@ @Test func sharePrivateTable() async throws { let error = await #expect(throws: (any Error).self) { _ = try await self.syncEngine.share( - record: RemindersListPrivate(id: 1, remindersListID: 1), + record: RemindersListPrivate(remindersListID: 1), configure: { _ in } ) } @@ -151,6 +151,58 @@ } } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func privateTableNotShared() async throws { + let remindersList = RemindersList(id: 1, title: "Personal") + try await userDatabase.userWrite { db in + try db.seed { + remindersList + RemindersListPrivate(remindersListID: 1, position: 42) + } + } + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + + let _ = try await syncEngine.share(record: remindersList, configure: { _ in }) + + 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:remindersListPrivates/zone/__defaultOwner__), + recordType: "remindersListPrivates", + parent: nil, + share: nil, + position: 42, + remindersListID: 1 + ), + [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 shareRecordBeforeSync() async throws { let error = await #expect(throws: (any Error).self) { diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index 807c2251..c20fc295 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -255,7 +255,7 @@ AFTER DELETE ON "remindersListPrivates" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN DELETE FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("old"."id")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersListPrivates')); + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("old"."remindersListID")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersListPrivates')); END """, [17]: """ @@ -276,7 +276,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"())) AND (("rootShares"."parentRecordName") IS (NULL))) AND (NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); UPDATE "sqlitedata_icloud_metadata" SET "_isDeleted" = 1 - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("old"."id")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersListPrivates')); + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("old"."remindersListID")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersListPrivates')); END """, [18]: """ @@ -626,7 +626,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"())) AND (("rootShares"."parentRecordName") IS (NULL))) AND (NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'remindersListPrivates', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."remindersListID", 'remindersListPrivates', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("new"."remindersListID")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersLists')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" FROM "sqlitedata_icloud_metadata" @@ -878,8 +878,8 @@ """, [47]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_remindersListPrivates" - AFTER UPDATE OF "id" ON "remindersListPrivates" - FOR EACH ROW WHEN ("old"."id") <> ("new"."id") BEGIN + AFTER UPDATE OF "remindersListID" ON "remindersListPrivates" + FOR EACH ROW WHEN ("old"."remindersListID") <> ("new"."remindersListID") BEGIN WITH "rootShares" AS ( SELECT "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName", "sqlitedata_icloud_metadata"."share" AS "share" FROM "sqlitedata_icloud_metadata" @@ -894,7 +894,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"())) AND (("rootShares"."parentRecordName") IS (NULL))) AND (NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); UPDATE "sqlitedata_icloud_metadata" SET "_isDeleted" = 1 - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("old"."id")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersListPrivates')); + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("old"."remindersListID")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersListPrivates')); END """, [48]: """ @@ -1230,7 +1230,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"())) AND (("rootShares"."parentRecordName") IS (NULL))) AND (NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'remindersListPrivates', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."remindersListID", 'remindersListPrivates', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("new"."remindersListID")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersLists')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" FROM "sqlitedata_icloud_metadata" @@ -1242,7 +1242,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("new"."remindersListID")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersLists')))), "sqlitedata_icloud_metadata"."zoneName"), "ownerName" = coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" FROM "sqlitedata_icloud_metadata" WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("new"."remindersListID")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersLists')))), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = "new"."remindersListID", "parentRecordType" = 'remindersLists', "userModificationTime" = "sqlitedata_icloud_currentTime"() - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("new"."id")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersListPrivates')); + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey") = ("new"."remindersListID")) AND (("sqlitedata_icloud_metadata"."recordType") = ('remindersListPrivates')); END """, [60]: """ diff --git a/Tests/SQLiteDataTests/Internal/Schema.swift b/Tests/SQLiteDataTests/Internal/Schema.swift index 5c0a7010..ba018d1c 100644 --- a/Tests/SQLiteDataTests/Internal/Schema.swift +++ b/Tests/SQLiteDataTests/Internal/Schema.swift @@ -23,9 +23,10 @@ import SQLiteData var id: RemindersList.ID { remindersListID } } @Table struct RemindersListPrivate: Equatable, Identifiable { - let id: Int - var position = 0 + @Column(primaryKey: true) var remindersListID: RemindersList.ID + var position = 0 + var id: RemindersList.ID { remindersListID } } @Table struct Tag: Equatable, Identifiable { @Column(primaryKey: true) @@ -112,9 +113,9 @@ func database( try #sql( """ CREATE TABLE "remindersListPrivates" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0, - "remindersListID" INTEGER NOT NULL REFERENCES "remindersLists"("id") ON DELETE CASCADE + "remindersListID" INTEGER PRIMARY KEY NOT NULL REFERENCES "remindersLists"("id") + ON DELETE CASCADE, + "position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0 ) STRICT """ )