diff --git a/Sources/SQLiteData/CloudKit/Internal/_SendableMetatype.swift b/Sources/SQLiteData/CloudKit/Internal/_SendableMetatype.swift new file mode 100644 index 00000000..64a0774e --- /dev/null +++ b/Sources/SQLiteData/CloudKit/Internal/_SendableMetatype.swift @@ -0,0 +1,5 @@ +#if swift(>=6.2) + public typealias _SendableMetatype = SendableMetatype +#else + public typealias _SendableMetatype = Any +#endif diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 71245e50..a79dc5c3 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -22,9 +22,9 @@ package let userDatabase: UserDatabase package let logger: Logger package let metadatabase: any DatabaseWriter - package let tables: [any PrimaryKeyedTable.Type] - package let privateTables: [any PrimaryKeyedTable.Type] - let tablesByName: [String: any PrimaryKeyedTable.Type] + package let tables: [any (PrimaryKeyedTable & _SendableMetatype).Type] + package let privateTables: [any (PrimaryKeyedTable & _SendableMetatype).Type] + let tablesByName: [String: any (PrimaryKeyedTable & _SendableMetatype).Type] private let tablesByOrder: [String: Int] let foreignKeysByTableName: [String: [ForeignKey]] package let syncEngines = LockIsolated(SyncEngines()) @@ -75,7 +75,7 @@ /// explicit call to ``stop()``. By default this argument is `true`. /// - logger: The logger used to log events in the sync engine. By default a `.disabled` /// logger is used, which means logs are not printed. - public convenience init( + public convenience init( for database: any DatabaseWriter, tables: repeat (each T1).Type, privateTables: repeat (each T2).Type, @@ -93,8 +93,8 @@ containerIdentifier ?? ModelConfiguration(groupContainer: .automatic).cloudKitContainerIdentifier - var allTables: [any PrimaryKeyedTable.Type] = [] - var allPrivateTables: [any PrimaryKeyedTable.Type] = [] + var allTables: [any (PrimaryKeyedTable & _SendableMetatype).Type] = [] + var allPrivateTables: [any (PrimaryKeyedTable & _SendableMetatype).Type] = [] for table in repeat each tables { allTables.append(table) } @@ -203,8 +203,8 @@ ) -> (private: any SyncEngineProtocol, shared: any SyncEngineProtocol), userDatabase: UserDatabase, logger: Logger, - tables: [any PrimaryKeyedTable.Type], - privateTables: [any PrimaryKeyedTable.Type] = [] + tables: [any (PrimaryKeyedTable & _SendableMetatype).Type], + privateTables: [any (PrimaryKeyedTable & _SendableMetatype).Type] = [] ) throws { let allTables = Set((tables + privateTables).map(HashablePrimaryKeyedTableType.init)) .map(\.type) @@ -1404,7 +1404,7 @@ let recordPrimaryKey = failedRecord.recordID.recordPrimaryKey, let table = tablesByName[failedRecord.recordType] else { continue } - func open(_: T.Type) async throws { + func open(_: T.Type) async throws { do { let serverRecord = try await container.sharedCloudDatabase.record( for: failedRecord.recordID @@ -1529,7 +1529,7 @@ serverRecord.userModificationDate = metadata?.userModificationDate ?? serverRecord.userModificationDate - func open(_: T.Type) async throws { + func open(_: T.Type) async throws { let columnNames: [String] if !force, let metadata, let allFields = metadata._lastKnownServerRecordAllFields { columnNames = try await userDatabase.read { db in @@ -1745,6 +1745,7 @@ package var isInMemory: Bool { path.isEmpty || path.hasPrefix(":memory:") + || absoluteString.hasPrefix(":memory:") || URLComponents(url: self, resolvingAgainstBaseURL: false)? .queryItems? .contains(where: { $0.name == "mode" && $0.value == "memory" }) @@ -1937,8 +1938,8 @@ } private struct HashablePrimaryKeyedTableType: Hashable { - let type: any PrimaryKeyedTable.Type - init(_ type: any PrimaryKeyedTable.Type) { + let type: any (PrimaryKeyedTable & _SendableMetatype).Type + init(_ type: any (PrimaryKeyedTable & _SendableMetatype).Type) { self.type = type } func hash(into hasher: inout Hasher) { @@ -1952,11 +1953,11 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) private func tablesByOrder( userDatabase: UserDatabase, - tables: [any PrimaryKeyedTable.Type], - tablesByName: [String: any PrimaryKeyedTable.Type] + tables: [any (PrimaryKeyedTable & _SendableMetatype).Type], + tablesByName: [String: any (PrimaryKeyedTable & _SendableMetatype).Type] ) throws -> [String: Int] { let tableDependencies = try userDatabase.read { db in - var dependencies: [HashablePrimaryKeyedTableType: [any PrimaryKeyedTable.Type]] = [:] + var dependencies: [HashablePrimaryKeyedTableType: [any (PrimaryKeyedTable & _SendableMetatype).Type]] = [:] for table in tables { func open(_: T.Type) throws -> [String] { try PragmaForeignKeyList.select(\.table) diff --git a/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift index bddfd88b..3df8dfc9 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift @@ -392,10 +392,10 @@ try withKnownIssue { _ = try syncEngine.modifyRecords(scope: .private, saving: [share]) } matching: { issue in - issue.description == """ - Issue recorded: An added share is being saved without its rootRecord being saved in the \ + issue.description.hasSuffix(""" + An added share is being saved without its rootRecord being saved in the \ same operation. - """ + """) } } diff --git a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift index 9292a102..d04474ff 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift @@ -8,7 +8,6 @@ import Testing extension BaseCloudKitTests { - @MainActor final class SchemaChangeTests: BaseCloudKitTests, @unchecked Sendable { @Dependency(\.dataManager) var dataManager var inMemoryDataManager: InMemoryDataManager { diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index 12ce91a5..22d1bd47 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -517,24 +517,31 @@ } } - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @Test func unshareNonSharedRecord() async throws { - let remindersList = RemindersList(id: 1, title: "Personal") - try await userDatabase.userWrite { db in - try db.seed { - remindersList + // NB: Swift 6.2 cannot currently compile this: + // Pattern that the region based isolation checker does not understand how to check. + // Please file a bug. + #if swift(<6.2) + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func unshareNonSharedRecord() 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) + + try await withKnownIssue { + try await syncEngine.unshare(record: remindersList) + } matching: { issue in + issue.description.hasSuffix( + """ + Issue recorded: No share found associated with record. + """ + ) } } - try await syncEngine.processPendingRecordZoneChanges(scope: .private) - - try await withKnownIssue { - try await syncEngine.unshare(record: remindersList) - } matching: { issue in - issue.description == """ - Issue recorded: No share found associated with record. - """ - } - } + #endif @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func shareUnshareShareAgain() async throws { @@ -629,7 +636,9 @@ ) _ = try syncEngine.modifyRecords(scope: .shared, saving: [share, remindersListRecord]) let freshShare = try syncEngine.shared.database.record(for: share.recordID) as! CKShare - let freshRemindersListRecord = try syncEngine.shared.database.record(for: remindersListRecord.recordID) + let freshRemindersListRecord = try syncEngine.shared.database.record( + for: remindersListRecord.recordID + ) try await syncEngine .acceptShare(