Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/SQLiteData/CloudKit/Internal/_SendableMetatype.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#if swift(>=6.2)
public typealias _SendableMetatype = SendableMetatype
#else
public typealias _SendableMetatype = Any
#endif
31 changes: 16 additions & 15 deletions Sources/SQLiteData/CloudKit/SyncEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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>(SyncEngines())
Expand Down Expand Up @@ -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<each T1: PrimaryKeyedTable, each T2: PrimaryKeyedTable>(
public convenience init<each T1: PrimaryKeyedTable & _SendableMetatype, each T2: PrimaryKeyedTable & _SendableMetatype>(
for database: any DatabaseWriter,
tables: repeat (each T1).Type,
privateTables: repeat (each T2).Type,
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1404,7 +1404,7 @@
let recordPrimaryKey = failedRecord.recordID.recordPrimaryKey,
let table = tablesByName[failedRecord.recordType]
else { continue }
func open<T: PrimaryKeyedTable>(_: T.Type) async throws {
func open<T: PrimaryKeyedTable & _SendableMetatype>(_: T.Type) async throws {
do {
let serverRecord = try await container.sharedCloudDatabase.record(
for: failedRecord.recordID
Expand Down Expand Up @@ -1529,7 +1529,7 @@
serverRecord.userModificationDate =
metadata?.userModificationDate ?? serverRecord.userModificationDate

func open<T: PrimaryKeyedTable>(_: T.Type) async throws {
func open<T: PrimaryKeyedTable & _SendableMetatype>(_: T.Type) async throws {
let columnNames: [String]
if !force, let metadata, let allFields = metadata._lastKnownServerRecordAllFields {
columnNames = try await userDatabase.read { db in
Expand Down Expand Up @@ -1745,6 +1745,7 @@
package var isInMemory: Bool {
path.isEmpty
|| path.hasPrefix(":memory:")
|| absoluteString.hasPrefix(":memory:")
Comment on lines 1747 to +1748
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am finding that for a URL like URL(string: ":memory:") the path is just "memory:", i.e. the leading ":" is removed. But in a URL like URL(string: "file::memory:") it is not. And so I have found that checking both path and absoluteString seems to cover the situations we are interested in, but there may be a better way.

|| URLComponents(url: self, resolvingAgainstBaseURL: false)?
.queryItems?
.contains(where: { $0.name == "mode" && $0.value == "memory" })
Expand Down Expand Up @@ -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) {
Expand All @@ -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: StructuredQueriesCore.Table>(_: T.Type) throws -> [String] {
try PragmaForeignKeyList<T>.select(\.table)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("""
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to start just doing suffix checks since Testing now puts the issue level in its messages.

An added share is being saved without its rootRecord being saved in the \
same operation.
"""
""")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import Testing

extension BaseCloudKitTests {
@MainActor
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was causing sendability problems in the test that I think are probably just Swift bugs. But we don't actually need @MainActor here, so can remove.

final class SchemaChangeTests: BaseCloudKitTests, @unchecked Sendable {
@Dependency(\.dataManager) var dataManager
var inMemoryDataManager: InMemoryDataManager {
Expand Down
43 changes: 26 additions & 17 deletions Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the comment above says, Swift 6.2 can't handle this for some reason.

} 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 {
Expand Down Expand Up @@ -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(
Expand Down