diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3e56ff26..40994f54 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" : "8b698458b719345f562ad056ad435aa3db5d6e852f96e6ca49566c5d31ffb528", + "originHash" : "c133bf7d10c8ce1e5d6506c3d2f080eac8b4c8c2827044d53a9b925e903564fd", "pins" : [ { "identity" : "combine-schedulers", @@ -73,24 +73,6 @@ "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", @@ -123,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-sharing", "state" : { - "revision" : "aaf605733bd93126d1a3658b4021146d95c94cb6", - "version" : "2.7.3" + "revision" : "3bfc408cc2d0bee2287c174da6b1c76768377818", + "version" : "2.7.4" } }, { @@ -154,6 +136,15 @@ "version" : "601.0.1" } }, + { + "identity" : "swift-tagged", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-tagged", + "state" : { + "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", + "version" : "0.10.0" + } + }, { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", diff --git a/Examples/Reminders/TagsForm.swift b/Examples/Reminders/TagsForm.swift index 1a60166e..c40b777a 100644 --- a/Examples/Reminders/TagsForm.swift +++ b/Examples/Reminders/TagsForm.swift @@ -151,7 +151,7 @@ private struct TagView: View { } label: { HStack { if isSelected { - Image.init(systemName: "checkmark") + Image(systemName: "checkmark") } Text(tag.title) } diff --git a/Package.swift b/Package.swift index c1d9d21d..73e0aeeb 100644 --- a/Package.swift +++ b/Package.swift @@ -24,7 +24,7 @@ let package = Package( .trait( name: "SQLiteDataTagged", description: "Introduce SQLiteData conformances to the swift-tagged package." - ), + ) ], dependencies: [ .package(url: "https://github.com/apple/swift-collections", from: "1.0.0"), diff --git a/README.md b/README.md index 29898d0e..f7a9e89e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLiteData -A [fast](#Performance), lightweight replacement for SwiftData, powered by SQL and supporting +A [fast](#Performance), lightweight replacement for SwiftData, powered by SQL and supporting CloudKit synchronization. [![CI](https://github.com/pointfreeco/sqlite-data/actions/workflows/ci.yml/badge.svg)](https://github.com/pointfreeco/sqlite-data/actions/workflows/ci.yml) @@ -353,8 +353,8 @@ SQLiteData. Check out [this](./Examples) directory to see them all, including: * [**CloudKitDemo**](./Examples/CloudKitDemo)
A simplified demo that shows how to synchronize a SQLite database to CloudKit and how to share records with other iCloud users. See our dedicated articles on [CloudKit Synchronization] - and [CloudKit Sharing] for more information. - + and [CloudKit Sharing] for more information. + [CloudKit Synchronization]: https://swiftpackageindex.com/pointfreeco/sqlite-data/main/documentation/sqlitedata/cloudkit [CloudKit Sharing]: https://swiftpackageindex.com/pointfreeco/sqlite-data/main/documentation/sqlitedata/cloudkitsharing diff --git a/Sources/SQLiteData/CloudKit/CloudKitSharing.swift b/Sources/SQLiteData/CloudKit/CloudKitSharing.swift index eac7c4a3..1e2d25ec 100644 --- a/Sources/SQLiteData/CloudKit/CloudKitSharing.swift +++ b/Sources/SQLiteData/CloudKit/CloudKitSharing.swift @@ -126,7 +126,7 @@ } let rootRecord = - lastKnownServerRecord + lastKnownServerRecord ?? CKRecord( recordType: recordType, recordID: CKRecord.ID(recordName: recordName, zoneID: defaultZone.zoneID) @@ -292,7 +292,7 @@ public func cloudSharingControllerDidStopSharing(_ csc: UICloudSharingController) { Task { await withErrorReporting(.sqliteDataCloudKitFailure) { - try await syncEngine.deleteShare(recordID: share.recordID) + try await syncEngine.deleteShare(shareRecordID: share.recordID) } } didStopSharing() diff --git a/Sources/SQLiteData/CloudKit/Internal/Metadatabase.swift b/Sources/SQLiteData/CloudKit/Internal/Metadatabase.swift index f6647277..d7198cb2 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Metadatabase.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Metadatabase.swift @@ -141,7 +141,7 @@ assert( !hasSchemaChanges, """ - A previously run migration has been removed or edited. + A previously run migration has been removed or edited. \ Metadatabase migrations must not be modified after release. """ ) diff --git a/Sources/SQLiteData/CloudKit/Internal/MockCloudContainer.swift b/Sources/SQLiteData/CloudKit/Internal/MockCloudContainer.swift index 4b75e98c..faea3f1b 100644 --- a/Sources/SQLiteData/CloudKit/Internal/MockCloudContainer.swift +++ b/Sources/SQLiteData/CloudKit/Internal/MockCloudContainer.swift @@ -105,7 +105,7 @@ package final class MockCloudContainer: CloudContainer, CustomDumpReflectable { } package var customDumpMirror: Mirror { - Mirror.init( + Mirror( self, children: [ ("privateCloudDatabase", privateCloudDatabase), diff --git a/Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift b/Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift index 4b7fad3a..1eca4a4d 100644 --- a/Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift @@ -58,7 +58,8 @@ package final class MockSyncEngine: SyncEngineProtocol { package func sendChanges(_ options: CKSyncEngine.SendChangesOptions) async throws { guard - !parentSyncEngine.syncEngine(for: database.databaseScope).state.pendingRecordZoneChanges.isEmpty + !parentSyncEngine.syncEngine(for: database.databaseScope).state.pendingRecordZoneChanges + .isEmpty else { return } try await parentSyncEngine.processPendingRecordZoneChanges(scope: database.databaseScope) } diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index 24ec4200..16077e14 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -55,7 +55,7 @@ parentForeignKey: parentForeignKey, defaultZone: defaultZone ) - SyncMetadata.upsert( + SyncMetadata.insert( new: new, parentForeignKey: parentForeignKey, defaultZone: defaultZone @@ -77,7 +77,12 @@ parentForeignKey: parentForeignKey, defaultZone: defaultZone ) - SyncMetadata.upsert( + SyncMetadata.insert( + new: new, + parentForeignKey: parentForeignKey, + defaultZone: defaultZone + ) + SyncMetadata.update( new: new, parentForeignKey: parentForeignKey, defaultZone: defaultZone @@ -133,7 +138,7 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) extension SyncMetadata { - fileprivate static func upsert( + fileprivate static func insert( new: StructuredQueriesCore.TableAlias.TableColumns, parentForeignKey: ForeignKey?, defaultZone: CKRecordZone @@ -143,6 +148,14 @@ parentForeignKey: parentForeignKey, defaultZone: defaultZone ) + let defaultZoneName = #sql( + "\(quote: defaultZone.zoneID.zoneName, delimiter: .text)", + as: String.self + ) + let defaultOwnerName = #sql( + "\(quote: defaultZone.zoneID.ownerName, delimiter: .text)", + as: String.self + ) return insert { ( $0.recordPrimaryKey, @@ -156,17 +169,35 @@ Values( #sql("\(new.primaryKey)"), T.tableName, - zoneName, - ownerName, + zoneName ?? defaultZoneName, + ownerName ?? defaultOwnerName, parentRecordPrimaryKey, parentRecordType ) - } onConflict: { - ($0.recordPrimaryKey, $0.recordType) - } doUpdate: { - $0.parentRecordPrimaryKey = $1.parentRecordPrimaryKey - $0.parentRecordType = $1.parentRecordType - $0.userModificationTime = $1.userModificationTime + } onConflictDoUpdate: { _ in + } + } + + fileprivate static func update( + new: StructuredQueriesCore.TableAlias.TableColumns, + parentForeignKey: ForeignKey?, + defaultZone: CKRecordZone + ) -> some StructuredQueriesCore.Statement { + let (parentRecordPrimaryKey, parentRecordType, zoneName, ownerName) = parentFields( + alias: new, + parentForeignKey: parentForeignKey, + defaultZone: defaultZone + ) + return Self.where { + $0.recordPrimaryKey.eq(#sql("\(new.primaryKey)")) + && $0.recordType.eq(T.tableName) + } + .update { + $0.zoneName = zoneName ?? $0.zoneName + $0.ownerName = ownerName ?? $0.ownerName + $0.parentRecordPrimaryKey = parentRecordPrimaryKey + $0.parentRecordType = parentRecordType + $0.userModificationTime = $currentTime() } } } @@ -176,31 +207,27 @@ static func callbackTriggers(for syncEngine: SyncEngine) -> [TemporaryTrigger] { [ afterInsertTrigger(for: syncEngine), + afterZoneUpdateTrigger(), afterUpdateTrigger(for: syncEngine), afterSoftDeleteTrigger(for: syncEngine), ] } - private enum ParentSyncMetadata: AliasName {} - fileprivate static func afterInsertTrigger(for syncEngine: SyncEngine) -> TemporaryTrigger { createTemporaryTrigger( - "after_insert_on_sqlitedata_icloud_metadata", + "\(String.sqliteDataCloudKitSchemaName)_after_insert_on_sqlitedata_icloud_metadata", ifNotExists: true, after: .insert { new in validate(recordName: new.recordName) Values( syncEngine.$didUpdate( recordName: new.recordName, - lastKnownServerRecord: new.lastKnownServerRecord - ?? rootServerRecord(recordName: new.recordName), - newParentLastKnownServerRecord: parentLastKnownServerRecordIfShared( - parentRecordPrimaryKey: new.parentRecordPrimaryKey, - parentRecordType: new.parentRecordType - ), - parentRecordPrimaryKey: new.parentRecordPrimaryKey, - parentRecordType: new.parentRecordType + zoneName: new.zoneName, + ownerName: new.ownerName, + oldZoneName: new.zoneName, + oldOwnerName: new.ownerName, + descendantRecordNames: #bind(nil) ) ) } when: { _ in @@ -209,24 +236,58 @@ ) } + fileprivate static func afterZoneUpdateTrigger() -> TemporaryTrigger { + createTemporaryTrigger( + "\(String.sqliteDataCloudKitSchemaName)_after_zone_update_on_sqlitedata_icloud_metadata", + ifNotExists: true, + after: .update { + ($0.zoneName, $0.ownerName) + } forEachRow: { old, new in + let selfAndDescendantRecordNames = descendantRecordNames( + recordName: new.recordName, + includeSelf: true + ) { + $0.select(\.recordName) + } + SyncMetadata + .where { + $0.recordName.in(selfAndDescendantRecordNames) + } + .update { + $0.zoneName = new.zoneName + $0.ownerName = new.ownerName + $0.lastKnownServerRecord = nil + $0._lastKnownServerRecordAllFields = nil + } + } when: { old, new in + new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName) + } + ) + } + fileprivate static func afterUpdateTrigger(for syncEngine: SyncEngine) -> TemporaryTrigger { createTemporaryTrigger( - "after_update_on_sqlitedata_icloud_metadata", + "\(String.sqliteDataCloudKitSchemaName)_after_update_on_sqlitedata_icloud_metadata", ifNotExists: true, - after: .update { _, new in + after: .update { old, new in + let zoneChanged = new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName) + let descendantRecordNamesJSON = descendantRecordNames( + recordName: new.recordName, + includeSelf: false + ) { + $0.select { $0.recordName.jsonGroupArray() } + } + validate(recordName: new.recordName) Values( syncEngine.$didUpdate( recordName: new.recordName, - lastKnownServerRecord: new.lastKnownServerRecord - ?? rootServerRecord(recordName: new.recordName), - newParentLastKnownServerRecord: parentLastKnownServerRecordIfShared( - parentRecordPrimaryKey: new.parentRecordPrimaryKey, - parentRecordType: new.parentRecordType - ), - parentRecordPrimaryKey: new.parentRecordPrimaryKey, - parentRecordType: new.parentRecordType + zoneName: new.zoneName, + ownerName: new.ownerName, + oldZoneName: old.zoneName, + oldOwnerName: old.ownerName, + descendantRecordNames: Case().when(zoneChanged, then: descendantRecordNamesJSON) ) ) } when: { old, new in @@ -235,11 +296,11 @@ ) } - fileprivate static func afterSoftDeleteTrigger(for syncEngine: SyncEngine) -> TemporaryTrigger< - Self - > { + fileprivate static func afterSoftDeleteTrigger( + for syncEngine: SyncEngine + ) -> TemporaryTrigger { createTemporaryTrigger( - "after_delete_on_sqlitedata_icloud_metadata", + "\(String.sqliteDataCloudKitSchemaName)_after_delete_on_sqlitedata_icloud_metadata", ifNotExists: true, after: .update(of: \._isDeleted) { _, new in Values( @@ -265,17 +326,9 @@ ) -> ( parentRecordPrimaryKey: SQLQueryExpression?, parentRecordType: SQLQueryExpression?, - zoneName: SQLQueryExpression, - ownerName: SQLQueryExpression + zoneName: SQLQueryExpression, + ownerName: SQLQueryExpression ) { - let zoneName = #sql( - "\(quote: defaultZone.zoneID.zoneName, delimiter: .text)", - as: String.self - ) - let ownerName = #sql( - "\(quote: defaultZone.zoneID.ownerName, delimiter: .text)", - as: String.self - ) return parentForeignKey .map { foreignKey in @@ -284,20 +337,23 @@ as: String.self ) let parentRecordType = #sql("\(bind: foreignKey.table)", as: String.self) - let parentMetadata = - SyncMetadata - .where { - $0.recordPrimaryKey.eq(parentRecordPrimaryKey) - && $0.recordType.eq(parentRecordType) - } + let parentMetadata = SyncMetadata.where { + $0.recordPrimaryKey.eq(parentRecordPrimaryKey) + && $0.recordType.eq(parentRecordType) + } return ( parentRecordPrimaryKey, parentRecordType, - #sql("coalesce((\(parentMetadata.select(\.zoneName))), \(zoneName))"), - #sql("coalesce((\(parentMetadata.select(\.ownerName))), \(ownerName))") + #sql("coalesce(\($currentZoneName()), (\(parentMetadata.select(\.zoneName))))"), + #sql("coalesce(\($currentOwnerName()), (\(parentMetadata.select(\.ownerName))))") ) } - ?? (nil, nil, zoneName, ownerName) + ?? ( + nil, + nil, + SQLQueryExpression($currentZoneName()), + SQLQueryExpression($currentOwnerName()) + ) } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @@ -356,6 +412,40 @@ } } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + private func descendantRecordNames( + recordName: some QueryExpression, + includeSelf: Bool, + select: (Where) -> Select + ) -> some Statement { + With { + SyncMetadata + .where { $0.recordName.eq(recordName) } + .select { + DescendantMetadata.Columns(recordName: $0.recordName, parentRecordName: #bind(nil)) + } + .union( + all: true, + SyncMetadata + .select { + DescendantMetadata.Columns( + recordName: $0.recordName, + parentRecordName: $0.parentRecordName + ) + } + .join(DescendantMetadata.all) { $0.parentRecordName.eq($1.recordName) } + ) + } query: { + select( + DescendantMetadata.where { + if !includeSelf { + $0.recordName.neq(recordName) + } + } + ) + } + } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) private func rootServerRecord( recordName: some QueryExpression @@ -378,7 +468,7 @@ } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - private func parentLastKnownServerRecordIfShared( + private func parentLastKnownServerRecord( parentRecordPrimaryKey: some QueryExpression, parentRecordType: some QueryExpression ) -> some QueryExpression { @@ -406,4 +496,39 @@ substr(1, 1).neq("_") && octetLength().lte(255) && octetLength().eq(length()) } } + + @Table @Selection + private struct DescendantMetadata { + let recordName: String + let parentRecordName: String? + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Table @Selection + private struct AncestorMetadata { + let recordName: String + let parentRecordName: String? + @Column(as: CKRecord?.SystemFieldsRepresentation.self) + let lastKnownServerRecord: CKRecord? + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Table @Selection + struct RecordWithRoot { + let parentRecordName: String? + let recordName: String + @Column(as: CKRecord?.SystemFieldsRepresentation.self) + let lastKnownServerRecord: CKRecord? + let rootRecordName: String + @Column(as: CKRecord?.SystemFieldsRepresentation.self) + let rootLastKnownServerRecord: CKRecord? + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Table @Selection + private struct RootShare { + let parentRecordName: String? + @Column(as: CKShare?.SystemFieldsRepresentation.self) + let share: CKShare? + } #endif diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index c4f77e96..cc101c2a 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -330,6 +330,8 @@ db.add(function: $didUpdate) db.add(function: $didDelete) db.add(function: $hasPermission) + db.add(function: $currentZoneName) + db.add(function: $currentOwnerName) for trigger in SyncMetadata.callbackTriggers(for: self) { try trigger.execute(db) @@ -571,11 +573,6 @@ for trigger in SyncMetadata.callbackTriggers(for: self).reversed() { try trigger.drop().execute(db) } - db.remove(function: $hasPermission) - db.remove(function: $didDelete) - db.remove(function: $didUpdate) - db.remove(function: $syncEngineIsSynchronizingChanges) - db.remove(function: $currentTime) } try metadatabase.erase() try migrate(metadatabase: metadatabase) @@ -604,62 +601,71 @@ "sqlitedata_icloud_didUpdate", as: (( String, - CKRecord?.SystemFieldsRepresentation, - CKRecord?.SystemFieldsRepresentation, - String?, - String? + String, + String, + String, + String, + [String]?.JSONRepresentation ) -> Void).self ) func didUpdate( recordName: String, - lastKnownServerRecord: CKRecord?, - newParentLastKnownServerRecord: CKRecord?, - parentRecordPrimaryKey: String? = nil, - parentRecordType: String? = nil - ) throws { - let zoneID = lastKnownServerRecord?.recordID.zoneID ?? defaultZone.zoneID - let newZoneID = newParentLastKnownServerRecord?.recordID.zoneID - if let newZoneID, zoneID != newZoneID { - struct ZoneChangingError: Error, LocalizedError { - let recordName: String - let zoneID: CKRecordZone.ID - let newZoneID: CKRecordZone.ID - var errorDescription: String? { - """ - The record '\(recordName)' was moved from zone \ - '\(zoneID.zoneName)/\(zoneID.ownerName)' to \ - '\(newZoneID.zoneName)/\(newZoneID.ownerName)'. This is currently not supported in \ - SQLiteData. To work around, delete the record and then create a new record with its \ - new parent association. - """ - } + zoneName: String, + ownerName: String, + oldZoneName: String, + oldOwnerName: String, + descendantRecordNames: [String]? + ) { + var oldChanges: [CKSyncEngine.PendingRecordZoneChange] = [] + var newChanges: [CKSyncEngine.PendingRecordZoneChange] = [] + + let oldZoneID = CKRecordZone.ID(zoneName: oldZoneName, ownerName: oldOwnerName) + let zoneID = CKRecordZone.ID(zoneName: zoneName, ownerName: ownerName) + + if oldZoneID != zoneID { + oldChanges.append(.deleteRecord(CKRecord.ID(recordName: recordName, zoneID: oldZoneID))) + for descendantRecordName in descendantRecordNames ?? [] { + oldChanges.append( + .deleteRecord(CKRecord.ID(recordName: descendantRecordName, zoneID: oldZoneID)) + ) + } + newChanges.append(.saveRecord(CKRecord.ID(recordName: recordName, zoneID: zoneID))) + for descendantRecordName in descendantRecordNames ?? [] { + newChanges.append( + .saveRecord(CKRecord.ID(recordName: descendantRecordName, zoneID: zoneID)) + ) } - throw ZoneChangingError(recordName: recordName, zoneID: zoneID, newZoneID: newZoneID) + } else { + newChanges.append( + .saveRecord(CKRecord.ID(recordName: recordName, zoneID: zoneID)) + ) } - let change = CKSyncEngine.PendingRecordZoneChange.saveRecord( - CKRecord.ID( - recordName: recordName, - zoneID: zoneID - ) - ) guard isRunning else { - Task { + // TODO: Perform this work in a trigger instead of a task. + Task { [changes = oldChanges + newChanges] in await withErrorReporting(.sqliteDataCloudKitFailure) { try await userDatabase.write { db in try PendingRecordZoneChange - .insert { PendingRecordZoneChange(change) } + .insert { + for change in changes { + PendingRecordZoneChange(change) + } + } .execute(db) } } } return } - + let oldSyncEngine = self.syncEngines.withValue { + oldZoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared + } let syncEngine = self.syncEngines.withValue { zoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared } - syncEngine?.state.add(pendingRecordZoneChanges: [change]) + oldSyncEngine?.state.add(pendingRecordZoneChanges: oldChanges) + syncEngine?.state.add(pendingRecordZoneChanges: newChanges) } @DatabaseFunction( @@ -910,7 +916,7 @@ catching: { try await metadatabase.read { db in try SyncMetadata - .where { $0.recordName.eq(recordID.recordName) } + .find(recordID) .select { ($0, $0._lastKnownServerRecordAllFields) } .fetchOne(db) } @@ -959,7 +965,7 @@ record.parent = CKRecord.Reference( recordID: CKRecord.ID( recordName: parentRecordName, - zoneID: record.recordID.zoneID + zoneID: recordID.zoneID ), action: .none ) @@ -998,22 +1004,26 @@ return nil } } - let deletedRecordNames = deletedRecordIDs.map(\.recordName) let (sharesToDelete, recordsWithRoot): ([CKShare?], [(lastKnownServerRecord: CKRecord?, rootLastKnownServerRecord: CKRecord?)]) = await withErrorReporting(.sqliteDataCloudKitFailure) { - try await metadatabase.read { db in + guard !deletedRecordIDs.isEmpty + else { return ([], []) } + + return try await metadatabase.read { db in let sharesToDelete = try SyncMetadata - .where { $0.isShared && $0.recordName.in(deletedRecordNames) } + .findAll(deletedRecordIDs) + .where(\.isShared) .select(\.share) .fetchAll(db) let recordsWithRoot = try With { SyncMetadata - .where { $0.parentRecordName.is(nil) && $0.recordName.in(deletedRecordNames) } + .findAll(deletedRecordIDs) + .where { $0.parentRecordName.is(nil) } .select { RecordWithRoot.Columns( parentRecordName: $0.parentRecordName, @@ -1039,7 +1049,6 @@ ) } query: { RecordWithRoot - .where { $0.recordName.in(deletedRecordNames) } .select { ($0.lastKnownServerRecord, $0.rootLastKnownServerRecord) } } .fetchAll(db) @@ -1067,9 +1076,11 @@ } await withErrorReporting(.sqliteDataCloudKitFailure) { + guard !deletedRecordIDs.isEmpty + else { return } try await userDatabase.write { db in try SyncMetadata - .where { $0.recordName.in(deletedRecordNames) } + .findAll(deletedRecordIDs) .delete() .execute(db) } @@ -1211,15 +1222,15 @@ ) .mapValues { $0.map(\.recordID) } for (recordType, recordIDs) in deletedRecordIDsByRecordType { - let recordPrimaryKeys = recordIDs.compactMap(\.recordPrimaryKey) if let table = tablesByName[recordType] { func open(_: T.Type) async { await withErrorReporting(.sqliteDataCloudKitFailure) { try await userDatabase.write { db in try T .where { - $0.primaryKey.in( - recordPrimaryKeys.map { #sql("\(bind: $0)") } + #sql("\($0.primaryKey)").in( + SyncMetadata.findAll(recordIDs) + .select(\.recordPrimaryKey) ) } .delete() @@ -1234,9 +1245,9 @@ } await open(table) } else if recordType == CKRecord.SystemType.share { - for recordID in recordIDs { + for shareRecordID in recordIDs { await withErrorReporting(.sqliteDataCloudKitFailure) { - try await deleteShare(recordID: recordID) + try await deleteShare(shareRecordID: shareRecordID) } } } else { @@ -1359,7 +1370,7 @@ await withErrorReporting(.sqliteDataCloudKitFailure) { try await userDatabase.write { db in try SyncMetadata - .where { $0.recordName.eq(failedRecord.recordID.recordName) } + .find(failedRecord.recordID) .update { $0.setLastKnownServerRecord(nil) } .execute(db) } @@ -1513,24 +1524,29 @@ else { return } try await userDatabase.write { db in try SyncMetadata - .where { $0.recordName.eq(rootRecordID.recordName) } + .find(rootRecordID) .update { $0.share = share } .execute(db) } } - func deleteShare(recordID: CKRecord.ID) async throws { + func deleteShare(shareRecordID: CKRecord.ID) async throws { try await userDatabase.write { db in - let shareAndRecordName = + let shareAndRecordNameAndZone = try SyncMetadata .where(\.isShared) - .select { ($0.share, $0.recordName) } + .select { ($0.share, $0.recordName, $0.zoneName, $0.ownerName) } .fetchAll(db) - .first(where: { share, _ in share?.recordID == recordID }) ?? nil - guard let (_, recordName) = shareAndRecordName + .first(where: { share, _, _, _ in share?.recordID == shareRecordID }) ?? nil + guard let (_, recordName, zoneName, ownerName) = shareAndRecordNameAndZone else { return } try SyncMetadata - .where { $0.recordName.eq(recordName) } + .find( + CKRecord.ID( + recordName: recordName, + zoneID: CKRecordZone.ID(zoneName: zoneName, ownerName: ownerName) + ) + ) .update { $0.share = nil } .execute(db) } @@ -1553,70 +1569,66 @@ db: Database ) { withErrorReporting(.sqliteDataCloudKitFailure) { - guard let table = tablesByName[serverRecord.recordType] - else { - guard let recordPrimaryKey = serverRecord.recordID.recordPrimaryKey - else { return } - try SyncMetadata.insert { - SyncMetadata( - recordPrimaryKey: recordPrimaryKey, - recordType: serverRecord.recordType, - zoneName: serverRecord.recordID.zoneID.zoneName, - ownerName: serverRecord.recordID.zoneID.ownerName, - parentRecordPrimaryKey: serverRecord.parent?.recordID.recordPrimaryKey, - parentRecordType: serverRecord.parent?.recordID.tableName, - lastKnownServerRecord: serverRecord, - _lastKnownServerRecordAllFields: serverRecord, - share: nil, - userModificationTime: serverRecord.userModificationTime - ) - } onConflict: { - ($0.recordPrimaryKey, $0.recordType) - } doUpdate: { + guard let recordPrimaryKey = serverRecord.recordID.recordPrimaryKey + else { return } + + try SyncMetadata.insert { + SyncMetadata( + recordPrimaryKey: recordPrimaryKey, + recordType: serverRecord.recordType, + zoneName: serverRecord.recordID.zoneID.zoneName, + ownerName: serverRecord.recordID.zoneID.ownerName, + parentRecordPrimaryKey: serverRecord.parent?.recordID.recordPrimaryKey, + parentRecordType: serverRecord.parent?.recordID.tableName, + lastKnownServerRecord: serverRecord, + _lastKnownServerRecordAllFields: serverRecord, + share: nil, + userModificationTime: serverRecord.userModificationTime + ) + } onConflict: { + ($0.recordPrimaryKey, $0.recordType) + } doUpdate: { + if tablesByName[serverRecord.recordType] == nil { $0.setLastKnownServerRecord(serverRecord) + } else { + $0.zoneName = serverRecord.recordID.zoneID.zoneName + $0.ownerName = serverRecord.recordID.zoneID.ownerName } - .execute(db) + } + .execute(db) + + guard + let metadata = try SyncMetadata.find(serverRecord.recordID).fetchOne(db), + let table = tablesByName[serverRecord.recordType] + else { return } - let metadata = - try SyncMetadata - .where { $0.recordName.eq(serverRecord.recordID.recordName) } - .fetchOne(db) - serverRecord.userModificationTime = - metadata?.userModificationTime ?? serverRecord.userModificationTime + serverRecord.userModificationTime = metadata.userModificationTime func open(_: T.Type) throws { - let columnNames: [String] - if !force, let metadata, let allFields = metadata._lastKnownServerRecordAllFields { - var _columnNames = T.TableColumns.writableColumns.map(\.name) + var columnNames: [String] = T.TableColumns.writableColumns.map(\.name) + if !force, + let allFields = metadata._lastKnownServerRecordAllFields, let row = try T.find(#sql("\(bind: metadata.recordPrimaryKey)")).fetchOne(db) - if let row { - serverRecord.update( - with: allFields, - row: T(queryOutput: row), - columnNames: &_columnNames, - parentForeignKey: foreignKeysByTableName[T.tableName]?.count == 1 - ? foreignKeysByTableName[T.tableName]?.first - : nil - ) - } else { - reportIssue( - """ - Local database record could not be found for '\(serverRecord.recordID.recordName)'. - """ - ) - } - columnNames = _columnNames - } else { - columnNames = T.TableColumns.writableColumns.map(\.name) + { + serverRecord.update( + with: allFields, + row: T(queryOutput: row), + columnNames: &columnNames, + parentForeignKey: foreignKeysByTableName[T.tableName]?.count == 1 + ? foreignKeysByTableName[T.tableName]?.first + : nil + ) } do { - try #sql(upsert(T.self, record: serverRecord, columnNames: columnNames)).execute(db) + try $_currentZoneID.withValue(serverRecord.recordID.zoneID) { + try #sql(upsert(T.self, record: serverRecord, columnNames: columnNames)).execute(db) + } try UnsyncedRecordID.find(serverRecord.recordID).delete().execute(db) try SyncMetadata - .where { $0.recordName.eq(serverRecord.recordID.recordName) } + .find(serverRecord.recordID) .update { $0.setLastKnownServerRecord(serverRecord) } .execute(db) } catch { @@ -1638,35 +1650,25 @@ } private func refreshLastKnownServerRecord(_ record: CKRecord) async { - let metadata = await metadataFor(recordName: record.recordID.recordName) - - func updateLastKnownServerRecord() async { - await withErrorReporting(.sqliteDataCloudKitFailure) { - try await userDatabase.write { db in + await withErrorReporting(.sqliteDataCloudKitFailure) { + try await metadatabase.write { db in + let metadata = try SyncMetadata.find(record.recordID).fetchOne(db) + func updateLastKnownServerRecord() throws { try SyncMetadata - .where { $0.recordName.eq(record.recordID.recordName) } + .find(record.recordID) .update { $0.setLastKnownServerRecord(record) } .execute(db) } - } - } - if let lastKnownDate = metadata?.lastKnownServerRecord?.modificationDate { - if let recordDate = record.modificationDate, lastKnownDate < recordDate { - await updateLastKnownServerRecord() - } - } else { - await updateLastKnownServerRecord() - } - } - - private func metadataFor(recordName: String) async -> SyncMetadata? { - await withErrorReporting(.sqliteDataCloudKitFailure) { - try await metadatabase.read { db in - try SyncMetadata.where { $0.recordName.eq(recordName) }.fetchOne(db) + if let lastKnownDate = metadata?.lastKnownServerRecord?.modificationDate { + if let recordDate = record.modificationDate, lastKnownDate < recordDate { + try updateLastKnownServerRecord() + } + } else { + try updateLastKnownServerRecord() + } } } - ?? nil } private func updateQuery( @@ -1747,7 +1749,7 @@ extension CKRecord.ID { var tableName: String? { guard - let i = recordName.utf8.lastIndex(of: .init(ascii: ":")), + let i = recordName.utf8.lastIndex(of: UTF8.CodeUnit(ascii: ":")), let j = recordName.utf8.index(i, offsetBy: 1, limitedBy: recordName.utf8.endIndex) else { return nil } let recordTypeBytes = recordName.utf8[j...] @@ -1757,7 +1759,7 @@ var recordPrimaryKey: String? { guard - let i = recordName.utf8.lastIndex(of: .init(ascii: ":")) + let i = recordName.utf8.lastIndex(of: UTF8.CodeUnit(ascii: ":")) else { return nil } let recordPrimaryKeyBytes = recordName.utf8[.. String? { + _currentZoneID?.zoneName + } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @DatabaseFunction("sqlitedata_icloud_currentOwnerName") + func currentOwnerName() -> String? { + _currentZoneID?.ownerName + } #endif diff --git a/Sources/SQLiteData/CloudKit/SyncMetadata.swift b/Sources/SQLiteData/CloudKit/SyncMetadata.swift index c5e06857..44672565 100644 --- a/Sources/SQLiteData/CloudKit/SyncMetadata.swift +++ b/Sources/SQLiteData/CloudKit/SyncMetadata.swift @@ -85,35 +85,6 @@ public var userModificationTime: Int64 } - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @Table @Selection - struct AncestorMetadata { - let recordName: String - let parentRecordName: String? - @Column(as: CKRecord?.SystemFieldsRepresentation.self) - let lastKnownServerRecord: CKRecord? - } - - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @Table @Selection - struct RecordWithRoot { - let parentRecordName: String? - let recordName: String - @Column(as: CKRecord?.SystemFieldsRepresentation.self) - let lastKnownServerRecord: CKRecord? - let rootRecordName: String - @Column(as: CKRecord?.SystemFieldsRepresentation.self) - let rootLastKnownServerRecord: CKRecord? - } - - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @Table @Selection - struct RootShare { - let parentRecordName: String? - @Column(as: CKShare?.SystemFieldsRepresentation.self) - let share: CKShare? - } - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) extension SyncMetadata { package init( @@ -147,6 +118,24 @@ self.isShared = share != nil self.userModificationTime = userModificationTime } + + package static func find(_ recordID: CKRecord.ID) -> Where { + Self.where { + $0.recordName.eq(recordID.recordName) + && $0.zoneName.eq(recordID.zoneID.zoneName) + && $0.ownerName.eq(recordID.zoneID.ownerName) + } + } + + package static func findAll(_ recordIDs: some Collection) -> Where { + let condition: QueryFragment = recordIDs.map { + "(\(bind: $0.recordName), \(bind: $0.zoneID.zoneName), \(bind: $0.zoneID.ownerName))" + } + .joined(separator: ", ") + return Self.where { + #sql("(\($0.recordName), \($0.zoneName), \($0.ownerName)) IN (\(condition))") + } + } } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) diff --git a/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md b/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md index e9f1ead0..6e2c2350 100644 --- a/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md +++ b/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md @@ -287,7 +287,7 @@ has been added to the schema, it will populate the table with the cached records #### Adding columns > TL;DR: When adding columns to a table that has already been deployed to users' devices, you will -either need to make the column nullable, or a default value must be provided with an +either need to make the column nullable, or a default value must be provided with an `ON CONFLICT REPLACE` clause. As an example, suppose the 1.0 of your app shipped a table for a reminders list: diff --git a/Sources/SQLiteData/Documentation.docc/Articles/Observing.md b/Sources/SQLiteData/Documentation.docc/Articles/Observing.md index f57feb5f..f1ce70e5 100644 --- a/Sources/SQLiteData/Documentation.docc/Articles/Observing.md +++ b/Sources/SQLiteData/Documentation.docc/Articles/Observing.md @@ -1,7 +1,7 @@ # Observing changes to model data -Learn how to observe changes to your database in SwiftUI views, UIKit view controllers, and -more. +Learn how to observe changes to your database in SwiftUI views, UIKit view controllers, and +more. ## Overview diff --git a/Sources/SQLiteData/Documentation.docc/Articles/PreparingDatabase.md b/Sources/SQLiteData/Documentation.docc/Articles/PreparingDatabase.md index f1a21b36..eadbdac1 100644 --- a/Sources/SQLiteData/Documentation.docc/Articles/PreparingDatabase.md +++ b/Sources/SQLiteData/Documentation.docc/Articles/PreparingDatabase.md @@ -1,6 +1,6 @@ # Preparing a SQLite database -Learn how to create, configure and migrate the SQLite database that holds your application’s +Learn how to create, configure and migrate the SQLite database that holds your application’s data. ## Overview @@ -46,10 +46,10 @@ optional step: } ``` -One configuration you may want to enable is query tracing in order to log queries that are executed -in your application. This can be handy for tracking down long-running queries, or when more queries -execute than you expect. We also recommend only doing this in debug builds to avoid leaking -sensitive information when the app is running on a user's device, and we further recommend using +One configuration you may want to enable is query tracing in order to log queries that are executed +in your application. This can be handy for tracking down long-running queries, or when more queries +execute than you expect. We also recommend only doing this in debug builds to avoid leaking +sensitive information when the app is running on a user's device, and we further recommend using OSLog when running your app in the simulator/device and using `Swift.print` in previews: ```diff @@ -85,7 +85,7 @@ OSLog when running your app in the simulator/device and using `Swift.print` in p [swift-dependencies-gh]: https://github.com/pointfreeco/swift-dependencies -For more information on configuring the database connection, see [GRDB's documentation][config-docs] +For more information on configuring the database connection, see [GRDB's documentation][config-docs] on the matter. [config-docs]: https://swiftpackageindex.com/groue/grdb.swift/master/documentation/grdb/configuration diff --git a/Sources/SQLiteData/Documentation.docc/SQLiteData.md b/Sources/SQLiteData/Documentation.docc/SQLiteData.md index 2112fd3d..c9a3bc32 100644 --- a/Sources/SQLiteData/Documentation.docc/SQLiteData.md +++ b/Sources/SQLiteData/Documentation.docc/SQLiteData.md @@ -1,6 +1,6 @@ # ``SQLiteData`` -A fast, lightweight replacement for SwiftData, powered by SQL and supporting CloudKit +A fast, lightweight replacement for SwiftData, powered by SQL and supporting CloudKit synchronization. ## Overview diff --git a/Tests/SQLiteDataTests/AssertQueryTests.swift b/Tests/SQLiteDataTests/AssertQueryTests.swift index 4878a923..4dfb959a 100644 --- a/Tests/SQLiteDataTests/AssertQueryTests.swift +++ b/Tests/SQLiteDataTests/AssertQueryTests.swift @@ -7,7 +7,7 @@ import Testing @Suite( .dependency(\.defaultDatabase, try .database()), - .snapshots(record: .failed), + .snapshots(record: .missing), ) struct AssertQueryTests { @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) diff --git a/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift index f8670788..e5865a70 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift @@ -357,7 +357,8 @@ _ = 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( diff --git a/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift index 59033c08..e6af0fd2 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift @@ -23,7 +23,8 @@ RemindersList(id: 1, title: "Personal") } } - defaultNotificationCenter.post(name: UIApplication.willResignActiveNotification, object: nil) + defaultNotificationCenter.post( + name: UIApplication.willResignActiveNotification, object: nil) try await Task.sleep(for: .seconds(1)) assertInlineSnapshot(of: container, as: .customDump) { """ @@ -90,7 +91,8 @@ } } - defaultNotificationCenter.post(name: UIApplication.willResignActiveNotification, object: nil) + defaultNotificationCenter.post( + name: UIApplication.willResignActiveNotification, object: nil) try await Task.sleep(for: .seconds(1)) assertInlineSnapshot(of: container, as: .customDump) { """ diff --git a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift index 57eb9b3c..460c27da 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift @@ -447,11 +447,13 @@ ) { """ [ - [0]: "sqlitedata_icloud_currenttime", - [1]: "sqlitedata_icloud_diddelete", - [2]: "sqlitedata_icloud_didupdate", - [3]: "sqlitedata_icloud_haspermission", - [4]: "sqlitedata_icloud_syncengineissynchronizingchanges" + [0]: "sqlitedata_icloud_currentownername", + [1]: "sqlitedata_icloud_currenttime", + [2]: "sqlitedata_icloud_currentzonename", + [3]: "sqlitedata_icloud_diddelete", + [4]: "sqlitedata_icloud_didupdate", + [5]: "sqlitedata_icloud_haspermission", + [6]: "sqlitedata_icloud_syncengineissynchronizingchanges" ] """ } diff --git a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift index 645540a0..e3a5964a 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift @@ -629,27 +629,26 @@ let reminderRecord = try syncEngine.private.database.record( for: Reminder.recordID(for: 1) ) - reminderRecord.setValue(Date( - timeIntervalSince1970: Double(now + 30)), + reminderRecord.setValue( + Date(timeIntervalSince1970: Double(now + 30)), forKey: "dueDate", - at: now + 1 + at: now ) let modificationsFinished = try syncEngine.modifyRecords( scope: .private, saving: [reminderRecord] ) - try withDependencies { - $0.currentTime.now += 2 + try await withDependencies { + $0.currentTime.now += 1 } operation: { - try userDatabase.userWrite { db in + try await userDatabase.userWrite { db in try Reminder.find(1).update { $0.priority = 3 }.execute(db) } + await modificationsFinished.notify() + try await syncEngine.processPendingRecordZoneChanges(scope: .private) } - await modificationsFinished.notify() - try await syncEngine.processPendingRecordZoneChanges(scope: .private) - assertInlineSnapshot(of: container, as: .customDump) { """ MockCloudContainer( @@ -662,18 +661,18 @@ parent: CKReference(recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__)), share: nil, dueDate: Date(1970-01-01T00:00:30.000Z), - dueDateπŸ—“οΈ: 1, + dueDateπŸ—“οΈ: 0, id: 1, idπŸ—“οΈ: 0, isCompleted: 0, isCompletedπŸ—“οΈ: 0, priority: 3, - priorityπŸ—“οΈ: 2, + priorityπŸ—“οΈ: 1, remindersListID: 1, remindersListIDπŸ—“οΈ: 0, title: "", titleπŸ—“οΈ: 0, - πŸ—“οΈ: 2 + πŸ—“οΈ: 1 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), diff --git a/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift index 3df8dfc9..963599f7 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift @@ -392,7 +392,8 @@ try withKnownIssue { _ = try syncEngine.modifyRecords(scope: .private, saving: [share]) } matching: { issue in - issue.description.hasSuffix(""" + issue.description.hasSuffix( + """ An added share is being saved without its rootRecord being saved in the \ same operation. """) diff --git a/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift b/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift index af13f787..839f2df7 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift @@ -48,7 +48,7 @@ .execute(db) } - try await syncEngine.processPendingRecordZoneChanges(scope: .private) + try await syncEngine.processPendingRecordZoneChanges(scope: .shared) assertInlineSnapshot(of: container, as: .customDump) { """ MockCloudContainer( diff --git a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift index fd1a0a10..20bf20a3 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift @@ -394,7 +394,7 @@ try #expect(RecordType.all.fetchCount(db) > 0) try #expect(StateSerialization.all.fetchCount(db) == 0) } - + try syncEngine.tearDownSyncEngine() try await syncEngine.metadatabase.read { db in try #expect(SyncMetadata.all.fetchCount(db) == 0) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index 67e7e714..9b4c2f54 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -835,18 +835,78 @@ try await syncEngine.processPendingRecordZoneChanges(scope: .shared) - assertQuery( - SyncMetadata.select { ($0.recordName, $0.parentRecordName) }, - database: syncEngine.metadatabase - ) { + assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { """ - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ "1:remindersLists" β”‚ nil β”‚ - β”‚ "1:reminders" β”‚ "1:remindersLists" β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "remindersLists", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:remindersLists", β”‚ + β”‚ parentRecordPrimaryKey: nil, β”‚ + β”‚ parentRecordType: nil, β”‚ + β”‚ parentRecordName: nil, β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), β”‚ + β”‚ recordType: "remindersLists", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-1:remindersLists/external.zone/external.owner)) β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), β”‚ + β”‚ recordType: "remindersLists", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-1:remindersLists/external.zone/external.owner)), β”‚ + β”‚ id: 1, β”‚ + β”‚ title: "Personal" β”‚ + β”‚ ), β”‚ + β”‚ share: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(share-1:remindersLists/external.zone/external.owner), β”‚ + β”‚ recordType: "cloudkit.share", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: true, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "reminders", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:reminders", β”‚ + β”‚ parentRecordPrimaryKey: "1", β”‚ + β”‚ parentRecordType: "remindersLists", β”‚ + β”‚ parentRecordName: "1:remindersLists", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:reminders/external.zone/external.owner), β”‚ + β”‚ recordType: "reminders", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:reminders/external.zone/external.owner), β”‚ + β”‚ recordType: "reminders", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ isCompleted: 0, β”‚ + β”‚ remindersListID: 1, β”‚ + β”‚ title: "Get milk" β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ """ } - assertInlineSnapshot(of: container, as: .customDump) { """ MockCloudContainer( @@ -964,6 +1024,7 @@ try await userDatabase.userWrite { db in try db.seed { Reminder(id: 1, title: "Get milk", remindersListID: 1) + Reminder(id: 2, title: "Take a walk", remindersListID: 1) } } @@ -975,13 +1036,9 @@ try await syncEngine.processPendingRecordZoneChanges(scope: .shared) - assertQuery( - SyncMetadata.select { ($0.recordName, $0.share) }, - database: syncEngine.metadatabase - ) { - """ - """ - } + assertQuery(Reminder.all, database: userDatabase.database) + assertQuery(RemindersList.all, database: userDatabase.database) + assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) assertInlineSnapshot(of: container, as: .customDump) { """ @@ -1004,6 +1061,16 @@ title: "Get milk" ), [1]: CKRecord( + recordID: CKRecord.ID(2:reminders/external.zone/external.owner), + recordType: "reminders", + parent: CKReference(recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner)), + share: nil, + id: 2, + isCompleted: 0, + remindersListID: 1, + title: "Take a walk" + ), + [2]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, @@ -1018,12 +1085,69 @@ } } + // NB: Come back to this when we have time to investigate. + // /// Deleting a root shared record that is not owned by current user should only delete + // /// the CKShare, not delete the actual CloudKit records, but delete all the local records. + // @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + // @Test func deleteRootSharedRecord_OnDeleteSetNull() async throws { + // let externalZone = CKRecordZone( + // zoneID: CKRecordZone.ID( + // zoneName: "external.zone", + // ownerName: "external.owner" + // ) + // ) + // try await syncEngine.modifyRecordZones(scope: .shared, saving: [externalZone]).notify() + // + // let parentRecord = CKRecord( + // recordType: Parent.tableName, + // recordID: Parent.recordID(for: 1, zoneID: externalZone.zoneID) + // ) + // parentRecord.setValue(1, forKey: "id", at: now) + // let share = CKShare( + // rootRecord: parentRecord, + // shareID: CKRecord.ID( + // recordName: "share-\(parentRecord.recordID.recordName)", + // zoneID: parentRecord.recordID.zoneID + // ) + // ) + // + // try await syncEngine + // .acceptShare( + // metadata: ShareMetadata( + // containerIdentifier: container.containerIdentifier!, + // hierarchicalRootRecordID: parentRecord.recordID, + // rootRecord: parentRecord, + // share: share + // ) + // ) + // + // try await userDatabase.userWrite { db in + // try db.seed { + // ChildWithOnDeleteSetNull(id: 1, parentID: 1) + // } + // } + // + // try await syncEngine.processPendingRecordZoneChanges(scope: .shared) + // + // try await userDatabase.userWrite { db in + // try Parent.find(1).delete().execute(db) + // } + // + // try await syncEngine.processPendingRecordZoneChanges(scope: .shared) + // + // assertQuery(Parent.all, database: userDatabase.database) + // assertQuery(ChildWithOnDeleteSetNull.all, database: userDatabase.database) + // assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) + // assertInlineSnapshot(of: container, as: .customDump) + // } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func movesChildRecordFromPrivateParentToSharedParent() async throws { try await userDatabase.userWrite { db in try db.seed { ModelA.Draft(id: 1, count: 42) ModelB.Draft(id: 1, isOn: true, modelAID: 1) + ModelC.Draft(id: 1, title: "Blob", modelBID: 1) } } try await syncEngine.processPendingRecordZoneChanges(scope: .private) @@ -1063,16 +1187,16 @@ ) ) - let error = await #expect(throws: DatabaseError.self) { + try await withDependencies { + $0.currentTime.now += 1 + } operation: { try await self.userDatabase.userWrite { db in try ModelB.find(1).update { $0.modelAID = 2 }.execute(db) } + + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + try await syncEngine.processPendingRecordZoneChanges(scope: .shared) } - #expect(error?.message == """ - The record '1:modelBs' was moved from zone 'zone/__defaultOwner__' to \ - 'external.zone/external.owner'. This is currently not supported in SQLiteData. To work \ - around, delete the record and then create a new record with its new parent association. - """) assertQuery(ModelB.all, database: userDatabase.database) { """ @@ -1080,12 +1204,26 @@ β”‚ ModelB( β”‚ β”‚ id: 1, β”‚ β”‚ isOn: true, β”‚ - β”‚ modelAID: 1 β”‚ + β”‚ modelAID: 2 β”‚ β”‚ ) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ """ } - assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { + assertQuery(ModelC.all, database: userDatabase.database) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ ModelC( β”‚ + β”‚ id: 1, β”‚ + β”‚ title: "Blob", β”‚ + β”‚ modelBID: 1 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertQuery( + SyncMetadata.order { ($0.recordType, $0.recordName) }, + database: syncEngine.metadatabase + ) { """ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ SyncMetadata( β”‚ @@ -1119,37 +1257,6 @@ β”‚ ) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ SyncMetadata( β”‚ - β”‚ recordPrimaryKey: "1", β”‚ - β”‚ recordType: "modelBs", β”‚ - β”‚ zoneName: "zone", β”‚ - β”‚ ownerName: "__defaultOwner__", β”‚ - β”‚ recordName: "1:modelBs", β”‚ - β”‚ parentRecordPrimaryKey: "1", β”‚ - β”‚ parentRecordType: "modelAs", β”‚ - β”‚ parentRecordName: "1:modelAs", β”‚ - β”‚ lastKnownServerRecord: CKRecord( β”‚ - β”‚ recordID: CKRecord.ID(1:modelBs/zone/__defaultOwner__), β”‚ - β”‚ recordType: "modelBs", β”‚ - β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__)), β”‚ - β”‚ share: nil β”‚ - β”‚ ), β”‚ - β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ - β”‚ recordID: CKRecord.ID(1:modelBs/zone/__defaultOwner__), β”‚ - β”‚ recordType: "modelBs", β”‚ - β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__)), β”‚ - β”‚ share: nil, β”‚ - β”‚ id: 1, β”‚ - β”‚ isOn: 1, β”‚ - β”‚ modelAID: 1 β”‚ - β”‚ ), β”‚ - β”‚ share: nil, β”‚ - β”‚ _isDeleted: false, β”‚ - β”‚ hasLastKnownServerRecord: true, β”‚ - β”‚ isShared: false, β”‚ - β”‚ userModificationTime: 0 β”‚ - β”‚ ) β”‚ - β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ - β”‚ SyncMetadata( β”‚ β”‚ recordPrimaryKey: "2", β”‚ β”‚ recordType: "modelAs", β”‚ β”‚ zoneName: "external.zone", β”‚ @@ -1183,6 +1290,68 @@ β”‚ isShared: true, β”‚ β”‚ userModificationTime: 0 β”‚ β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:modelBs", β”‚ + β”‚ parentRecordPrimaryKey: "2", β”‚ + β”‚ parentRecordType: "modelAs", β”‚ + β”‚ parentRecordName: "2:modelAs", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ isOn: 1, β”‚ + β”‚ modelAID: 2 β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 1 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:modelCs", β”‚ + β”‚ parentRecordPrimaryKey: "1", β”‚ + β”‚ parentRecordType: "modelBs", β”‚ + β”‚ parentRecordName: "1:modelBs", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ modelBID: 1, β”‚ + β”‚ title: "Blob" β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ """ } @@ -1199,15 +1368,6 @@ share: nil, count: 42, id: 1 - ), - [1]: CKRecord( - recordID: CKRecord.ID(1:modelBs/zone/__defaultOwner__), - recordType: "modelBs", - parent: CKReference(recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__)), - share: nil, - id: 1, - isOn: 1, - modelAID: 1 ) ] ), @@ -1227,6 +1387,1121 @@ share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)), count: 1729, id: 2 + ), + [2]: CKRecord( + recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), + recordType: "modelBs", + parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), + share: nil, + id: 1, + isOn: 1, + modelAID: 2 + ), + [3]: CKRecord( + recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), + recordType: "modelCs", + parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), + share: nil, + id: 1, + modelBID: 1, + title: "Blob" + ) + ] + ) + ) + """ + } + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func movesChildRecordFromPrivateParentToSharedParent_ReceiveDeleteBeforeSave() + async throws + { + try await userDatabase.userWrite { db in + try db.seed { + ModelA.Draft(id: 1, count: 42) + ModelB.Draft(id: 1, isOn: true, modelAID: 1) + ModelC.Draft(id: 1, title: "Blob", modelBID: 1) + } + } + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + + let externalZone = CKRecordZone( + zoneID: CKRecordZone.ID( + zoneName: "external.zone", + ownerName: "external.owner" + ) + ) + try await syncEngine.modifyRecordZones(scope: .shared, saving: [externalZone]).notify() + + let modelARecord = CKRecord( + recordType: ModelA.tableName, + recordID: ModelA.recordID(for: 2, zoneID: externalZone.zoneID) + ) + modelARecord.setValue(2, forKey: "id", at: now) + modelARecord.setValue(1729, forKey: "count", at: now) + let share = CKShare( + rootRecord: modelARecord, + shareID: CKRecord.ID( + recordName: "share-\(modelARecord.recordID.recordName)", + zoneID: modelARecord.recordID.zoneID + ) + ) + _ = try syncEngine.modifyRecords(scope: .shared, saving: [share, modelARecord]) + let freshShare = try syncEngine.shared.database.record(for: share.recordID) as! CKShare + let freshModelARecord = try syncEngine.shared.database.record(for: modelARecord.recordID) + + try await syncEngine + .acceptShare( + metadata: ShareMetadata( + containerIdentifier: container.containerIdentifier!, + hierarchicalRootRecordID: freshModelARecord.recordID, + rootRecord: freshModelARecord, + share: freshShare + ) + ) + + let movedModelBRecord = CKRecord( + recordType: ModelB.tableName, + recordID: ModelB.recordID(for: 1, zoneID: externalZone.zoneID) + ) + movedModelBRecord.setValue(1, forKey: "id", at: now) + movedModelBRecord.setValue(true, forKey: "isOn", at: now) + movedModelBRecord.setValue(2, forKey: "modelAID", at: now) + movedModelBRecord.parent = CKRecord.Reference( + recordID: ModelA.recordID(for: 2, zoneID: externalZone.zoneID), + action: .none + ) + let movedModelCRecord = CKRecord( + recordType: ModelC.tableName, + recordID: ModelC.recordID(for: 1, zoneID: externalZone.zoneID) + ) + movedModelCRecord.setValue(1, forKey: "id", at: now) + movedModelCRecord.setValue("Blob", forKey: "title", at: now) + movedModelCRecord.setValue(1, forKey: "modelBID", at: now) + movedModelCRecord.parent = CKRecord.Reference( + recordID: ModelB.recordID(for: 1, zoneID: externalZone.zoneID), + action: .none + ) + + try await syncEngine.modifyRecords( + scope: .private, + deleting: [ModelB.recordID(for: 1), ModelC.recordID(for: 1)] + ).notify() + try await syncEngine.modifyRecords( + scope: .shared, + saving: [movedModelBRecord, movedModelCRecord] + ).notify() + + assertQuery(ModelB.all, database: userDatabase.database) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ ModelB( β”‚ + β”‚ id: 1, β”‚ + β”‚ isOn: true, β”‚ + β”‚ modelAID: 2 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertQuery(ModelC.all, database: userDatabase.database) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ ModelC( β”‚ + β”‚ id: 1, β”‚ + β”‚ title: "Blob", β”‚ + β”‚ modelBID: 1 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertQuery( + SyncMetadata.order { ($0.recordType, $0.recordName) }, + database: syncEngine.metadatabase + ) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ zoneName: "zone", β”‚ + β”‚ ownerName: "__defaultOwner__", β”‚ + β”‚ recordName: "1:modelAs", β”‚ + β”‚ parentRecordPrimaryKey: nil, β”‚ + β”‚ parentRecordType: nil, β”‚ + β”‚ parentRecordName: nil, β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil, β”‚ + β”‚ count: 42, β”‚ + β”‚ id: 1 β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "2", β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "2:modelAs", β”‚ + β”‚ parentRecordPrimaryKey: nil, β”‚ + β”‚ parentRecordType: nil, β”‚ + β”‚ parentRecordName: nil, β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)) β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)), β”‚ + β”‚ count: 1729, β”‚ + β”‚ id: 2 β”‚ + β”‚ ), β”‚ + β”‚ share: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "cloudkit.share", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: true, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:modelBs", β”‚ + β”‚ parentRecordPrimaryKey: "2", β”‚ + β”‚ parentRecordType: "modelAs", β”‚ + β”‚ parentRecordName: "2:modelAs", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ isOn: 1, β”‚ + β”‚ modelAID: 2 β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:modelCs", β”‚ + β”‚ parentRecordPrimaryKey: "1", β”‚ + β”‚ parentRecordType: "modelBs", β”‚ + β”‚ parentRecordName: "1:modelBs", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ modelBID: 1, β”‚ + β”‚ title: "Blob" β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertInlineSnapshot(of: container, as: .customDump) { + """ + MockCloudContainer( + privateCloudDatabase: MockCloudDatabase( + databaseScope: .private, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), + recordType: "modelAs", + parent: nil, + share: nil, + count: 42, + id: 1 + ) + ] + ), + sharedCloudDatabase: MockCloudDatabase( + databaseScope: .shared, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner), + recordType: "cloudkit.share", + parent: nil, + share: nil + ), + [1]: CKRecord( + recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), + recordType: "modelAs", + parent: nil, + share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)), + count: 1729, + id: 2 + ), + [2]: CKRecord( + recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), + recordType: "modelBs", + parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), + share: nil, + id: 1, + isOn: 1, + modelAID: 2 + ), + [3]: CKRecord( + recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), + recordType: "modelCs", + parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), + share: nil, + id: 1, + modelBID: 1, + title: "Blob" + ) + ] + ) + ) + """ + } + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func movesChildRecordFromPrivateParentToSharedParent_ReceiveSaveBeforeDelete() + async throws + { + try await userDatabase.userWrite { db in + try db.seed { + ModelA.Draft(id: 1, count: 42) + ModelB.Draft(id: 1, isOn: true, modelAID: 1) + ModelC.Draft(id: 1, title: "Blob", modelBID: 1) + } + } + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + + let externalZone = CKRecordZone( + zoneID: CKRecordZone.ID( + zoneName: "external.zone", + ownerName: "external.owner" + ) + ) + try await syncEngine.modifyRecordZones(scope: .shared, saving: [externalZone]).notify() + + let modelARecord = CKRecord( + recordType: ModelA.tableName, + recordID: ModelA.recordID(for: 2, zoneID: externalZone.zoneID) + ) + modelARecord.setValue(2, forKey: "id", at: now) + modelARecord.setValue(1729, forKey: "count", at: now) + let share = CKShare( + rootRecord: modelARecord, + shareID: CKRecord.ID( + recordName: "share-\(modelARecord.recordID.recordName)", + zoneID: modelARecord.recordID.zoneID + ) + ) + _ = try syncEngine.modifyRecords(scope: .shared, saving: [share, modelARecord]) + let freshShare = try syncEngine.shared.database.record(for: share.recordID) as! CKShare + let freshModelARecord = try syncEngine.shared.database.record(for: modelARecord.recordID) + + try await syncEngine + .acceptShare( + metadata: ShareMetadata( + containerIdentifier: container.containerIdentifier!, + hierarchicalRootRecordID: freshModelARecord.recordID, + rootRecord: freshModelARecord, + share: freshShare + ) + ) + + let movedModelBRecord = CKRecord( + recordType: ModelB.tableName, + recordID: ModelB.recordID(for: 1, zoneID: externalZone.zoneID) + ) + movedModelBRecord.setValue(1, forKey: "id", at: now) + movedModelBRecord.setValue(true, forKey: "isOn", at: now) + movedModelBRecord.setValue(2, forKey: "modelAID", at: now) + movedModelBRecord.parent = CKRecord.Reference( + recordID: ModelA.recordID(for: 2, zoneID: externalZone.zoneID), + action: .none + ) + let movedModelCRecord = CKRecord( + recordType: ModelC.tableName, + recordID: ModelC.recordID(for: 1, zoneID: externalZone.zoneID) + ) + movedModelCRecord.setValue(1, forKey: "id", at: now) + movedModelCRecord.setValue("Blob", forKey: "title", at: now) + movedModelCRecord.setValue(1, forKey: "modelBID", at: now) + movedModelCRecord.parent = CKRecord.Reference( + recordID: ModelB.recordID(for: 1, zoneID: externalZone.zoneID), + action: .none + ) + + try await syncEngine.modifyRecords( + scope: .shared, + saving: [movedModelBRecord, movedModelCRecord] + ).notify() + try await syncEngine.modifyRecords( + scope: .private, + deleting: [ModelB.recordID(for: 1), ModelC.recordID(for: 1)] + ).notify() + + assertQuery(ModelB.all, database: userDatabase.database) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ ModelB( β”‚ + β”‚ id: 1, β”‚ + β”‚ isOn: true, β”‚ + β”‚ modelAID: 2 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertQuery(ModelC.all, database: userDatabase.database) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ ModelC( β”‚ + β”‚ id: 1, β”‚ + β”‚ title: "Blob", β”‚ + β”‚ modelBID: 1 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertQuery( + SyncMetadata.order { ($0.recordType, $0.recordName) }, + database: syncEngine.metadatabase + ) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ zoneName: "zone", β”‚ + β”‚ ownerName: "__defaultOwner__", β”‚ + β”‚ recordName: "1:modelAs", β”‚ + β”‚ parentRecordPrimaryKey: nil, β”‚ + β”‚ parentRecordType: nil, β”‚ + β”‚ parentRecordName: nil, β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil, β”‚ + β”‚ count: 42, β”‚ + β”‚ id: 1 β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "2", β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "2:modelAs", β”‚ + β”‚ parentRecordPrimaryKey: nil, β”‚ + β”‚ parentRecordType: nil, β”‚ + β”‚ parentRecordName: nil, β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)) β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)), β”‚ + β”‚ count: 1729, β”‚ + β”‚ id: 2 β”‚ + β”‚ ), β”‚ + β”‚ share: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "cloudkit.share", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: true, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:modelBs", β”‚ + β”‚ parentRecordPrimaryKey: "2", β”‚ + β”‚ parentRecordType: "modelAs", β”‚ + β”‚ parentRecordName: "2:modelAs", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ isOn: 1, β”‚ + β”‚ modelAID: 2 β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:modelCs", β”‚ + β”‚ parentRecordPrimaryKey: "1", β”‚ + β”‚ parentRecordType: "modelBs", β”‚ + β”‚ parentRecordName: "1:modelBs", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ modelBID: 1, β”‚ + β”‚ title: "Blob" β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertInlineSnapshot(of: container, as: .customDump) { + """ + MockCloudContainer( + privateCloudDatabase: MockCloudDatabase( + databaseScope: .private, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), + recordType: "modelAs", + parent: nil, + share: nil, + count: 42, + id: 1 + ) + ] + ), + sharedCloudDatabase: MockCloudDatabase( + databaseScope: .shared, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner), + recordType: "cloudkit.share", + parent: nil, + share: nil + ), + [1]: CKRecord( + recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), + recordType: "modelAs", + parent: nil, + share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)), + count: 1729, + id: 2 + ), + [2]: CKRecord( + recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), + recordType: "modelBs", + parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), + share: nil, + id: 1, + isOn: 1, + modelAID: 2 + ), + [3]: CKRecord( + recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), + recordType: "modelCs", + parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), + share: nil, + id: 1, + modelBID: 1, + title: "Blob" + ) + ] + ) + ) + """ + } + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func movesChildRecordFromSharedParentToPrivateParent() async throws { + try await userDatabase.userWrite { db in + try db.seed { + ModelA.Draft(id: 1, count: 42) + } + } + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + + let externalZone = CKRecordZone( + zoneID: CKRecordZone.ID( + zoneName: "external.zone", + ownerName: "external.owner" + ) + ) + try await syncEngine.modifyRecordZones(scope: .shared, saving: [externalZone]).notify() + + let modelARecord = CKRecord( + recordType: ModelA.tableName, + recordID: ModelA.recordID(for: 2, zoneID: externalZone.zoneID) + ) + modelARecord.setValue(2, forKey: "id", at: now) + modelARecord.setValue(1729, forKey: "count", at: now) + let share = CKShare( + rootRecord: modelARecord, + shareID: CKRecord.ID( + recordName: "share-\(modelARecord.recordID.recordName)", + zoneID: modelARecord.recordID.zoneID + ) + ) + let modelBRecord = CKRecord( + recordType: ModelB.tableName, + recordID: ModelB.recordID(for: 1, zoneID: externalZone.zoneID) + ) + modelBRecord.setValue(1, forKey: "id", at: now) + modelBRecord.setValue(true, forKey: "isOne", at: now) + modelBRecord.setValue(1, forKey: "modelAID", at: now) + modelBRecord.parent = CKRecord.Reference(record: modelARecord, action: .none) + + _ = + try syncEngine + .modifyRecords(scope: .shared, saving: [share, modelARecord, modelBRecord]) + let freshShare = try syncEngine.shared.database.record(for: share.recordID) as! CKShare + let freshModelARecord = try syncEngine.shared.database.record(for: modelARecord.recordID) + + try await syncEngine + .acceptShare( + metadata: ShareMetadata( + containerIdentifier: container.containerIdentifier!, + hierarchicalRootRecordID: freshModelARecord.recordID, + rootRecord: freshModelARecord, + share: freshShare + ) + ) + + try await withDependencies { + $0.currentTime.now += 1 + } operation: { + try await self.userDatabase.userWrite { db in + try ModelB.find(1).update { $0.modelAID = 1 }.execute(db) + } + + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + try await syncEngine.processPendingRecordZoneChanges(scope: .shared) + } + + assertQuery(ModelB.all, database: userDatabase.database) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ ModelB( β”‚ + β”‚ id: 1, β”‚ + β”‚ isOn: false, β”‚ + β”‚ modelAID: 1 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertQuery(ModelC.all, database: userDatabase.database) + assertQuery( + SyncMetadata.order { ($0.recordType, $0.recordName) }, + database: syncEngine.metadatabase + ) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ zoneName: "zone", β”‚ + β”‚ ownerName: "__defaultOwner__", β”‚ + β”‚ recordName: "1:modelAs", β”‚ + β”‚ parentRecordPrimaryKey: nil, β”‚ + β”‚ parentRecordType: nil, β”‚ + β”‚ parentRecordName: nil, β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil, β”‚ + β”‚ count: 42, β”‚ + β”‚ id: 1 β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "2", β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "2:modelAs", β”‚ + β”‚ parentRecordPrimaryKey: nil, β”‚ + β”‚ parentRecordType: nil, β”‚ + β”‚ parentRecordName: nil, β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)) β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)), β”‚ + β”‚ count: 1729, β”‚ + β”‚ id: 2 β”‚ + β”‚ ), β”‚ + β”‚ share: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "cloudkit.share", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: true, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ zoneName: "zone", β”‚ + β”‚ ownerName: "__defaultOwner__", β”‚ + β”‚ recordName: "1:modelBs", β”‚ + β”‚ parentRecordPrimaryKey: "1", β”‚ + β”‚ parentRecordType: "modelAs", β”‚ + β”‚ parentRecordName: "1:modelAs", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ isOn: 0, β”‚ + β”‚ modelAID: 1 β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 1 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertInlineSnapshot(of: container, as: .customDump) { + """ + MockCloudContainer( + privateCloudDatabase: MockCloudDatabase( + databaseScope: .private, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), + recordType: "modelAs", + parent: nil, + share: nil, + count: 42, + id: 1 + ), + [1]: CKRecord( + recordID: CKRecord.ID(1:modelBs/zone/__defaultOwner__), + recordType: "modelBs", + parent: CKReference(recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__)), + share: nil, + id: 1, + isOn: 0, + modelAID: 1 + ) + ] + ), + sharedCloudDatabase: MockCloudDatabase( + databaseScope: .shared, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner), + recordType: "cloudkit.share", + parent: nil, + share: nil + ), + [1]: CKRecord( + recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), + recordType: "modelAs", + parent: nil, + share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)), + count: 1729, + id: 2 + ) + ] + ) + ) + """ + } + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test + func movesChildRecordFromPrivateParentToSharedParentWhileSyncEngineStopped() async throws { + try await userDatabase.userWrite { db in + try db.seed { + ModelA.Draft(id: 1, count: 42) + ModelB.Draft(id: 1, isOn: true, modelAID: 1) + ModelC.Draft(id: 1, title: "Blob", modelBID: 1) + } + } + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + + let externalZone = CKRecordZone( + zoneID: CKRecordZone.ID( + zoneName: "external.zone", + ownerName: "external.owner" + ) + ) + try await syncEngine.modifyRecordZones(scope: .shared, saving: [externalZone]).notify() + + let modelARecord = CKRecord( + recordType: ModelA.tableName, + recordID: ModelA.recordID(for: 2, zoneID: externalZone.zoneID) + ) + modelARecord.setValue(2, forKey: "id", at: now) + modelARecord.setValue(1729, forKey: "count", at: now) + let share = CKShare( + rootRecord: modelARecord, + shareID: CKRecord.ID( + recordName: "share-\(modelARecord.recordID.recordName)", + zoneID: modelARecord.recordID.zoneID + ) + ) + _ = try syncEngine.modifyRecords(scope: .shared, saving: [share, modelARecord]) + let freshShare = try syncEngine.shared.database.record(for: share.recordID) as! CKShare + let freshModelARecord = try syncEngine.shared.database.record(for: modelARecord.recordID) + + try await syncEngine + .acceptShare( + metadata: ShareMetadata( + containerIdentifier: container.containerIdentifier!, + hierarchicalRootRecordID: freshModelARecord.recordID, + rootRecord: freshModelARecord, + share: freshShare + ) + ) + + syncEngine.stop() + + try await withDependencies { + $0.currentTime.now += 1 + } operation: { + try await self.userDatabase.userWrite { db in + try ModelB.find(1).update { $0.modelAID = 2 }.execute(db) + } + } + + try await syncEngine.start() + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + try await syncEngine.processPendingRecordZoneChanges(scope: .shared) + + assertQuery(ModelB.all, database: userDatabase.database) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ ModelB( β”‚ + β”‚ id: 1, β”‚ + β”‚ isOn: true, β”‚ + β”‚ modelAID: 2 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertQuery(ModelC.all, database: userDatabase.database) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ ModelC( β”‚ + β”‚ id: 1, β”‚ + β”‚ title: "Blob", β”‚ + β”‚ modelBID: 1 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertQuery( + SyncMetadata.order { ($0.recordType, $0.recordName) }, + database: syncEngine.metadatabase + ) { + """ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ zoneName: "zone", β”‚ + β”‚ ownerName: "__defaultOwner__", β”‚ + β”‚ recordName: "1:modelAs", β”‚ + β”‚ parentRecordPrimaryKey: nil, β”‚ + β”‚ parentRecordType: nil, β”‚ + β”‚ parentRecordName: nil, β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil, β”‚ + β”‚ count: 42, β”‚ + β”‚ id: 1 β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "2", β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "2:modelAs", β”‚ + β”‚ parentRecordPrimaryKey: nil, β”‚ + β”‚ parentRecordType: nil, β”‚ + β”‚ parentRecordName: nil, β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)) β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelAs", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)), β”‚ + β”‚ count: 1729, β”‚ + β”‚ id: 2 β”‚ + β”‚ ), β”‚ + β”‚ share: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner), β”‚ + β”‚ recordType: "cloudkit.share", β”‚ + β”‚ parent: nil, β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: true, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:modelBs", β”‚ + β”‚ parentRecordPrimaryKey: "2", β”‚ + β”‚ parentRecordType: "modelAs", β”‚ + β”‚ parentRecordName: "2:modelAs", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelBs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ isOn: 1, β”‚ + β”‚ modelAID: 2 β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 1 β”‚ + β”‚ ) β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ SyncMetadata( β”‚ + β”‚ recordPrimaryKey: "1", β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ zoneName: "external.zone", β”‚ + β”‚ ownerName: "external.owner", β”‚ + β”‚ recordName: "1:modelCs", β”‚ + β”‚ parentRecordPrimaryKey: "1", β”‚ + β”‚ parentRecordType: "modelBs", β”‚ + β”‚ parentRecordName: "1:modelBs", β”‚ + β”‚ lastKnownServerRecord: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), β”‚ + β”‚ share: nil β”‚ + β”‚ ), β”‚ + β”‚ _lastKnownServerRecordAllFields: CKRecord( β”‚ + β”‚ recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), β”‚ + β”‚ recordType: "modelCs", β”‚ + β”‚ parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), β”‚ + β”‚ share: nil, β”‚ + β”‚ id: 1, β”‚ + β”‚ modelBID: 1, β”‚ + β”‚ title: "Blob" β”‚ + β”‚ ), β”‚ + β”‚ share: nil, β”‚ + β”‚ _isDeleted: false, β”‚ + β”‚ hasLastKnownServerRecord: true, β”‚ + β”‚ isShared: false, β”‚ + β”‚ userModificationTime: 0 β”‚ + β”‚ ) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + """ + } + assertInlineSnapshot(of: container, as: .customDump) { + """ + MockCloudContainer( + privateCloudDatabase: MockCloudDatabase( + databaseScope: .private, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), + recordType: "modelAs", + parent: nil, + share: nil, + count: 42, + id: 1 + ) + ] + ), + sharedCloudDatabase: MockCloudDatabase( + databaseScope: .shared, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner), + recordType: "cloudkit.share", + parent: nil, + share: nil + ), + [1]: CKRecord( + recordID: CKRecord.ID(2:modelAs/external.zone/external.owner), + recordType: "modelAs", + parent: nil, + share: CKReference(recordID: CKRecord.ID(share-2:modelAs/external.zone/external.owner)), + count: 1729, + id: 2 + ), + [2]: CKRecord( + recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), + recordType: "modelBs", + parent: CKReference(recordID: CKRecord.ID(2:modelAs/external.zone/external.owner)), + share: nil, + id: 1, + isOn: 1, + modelAID: 2 + ), + [3]: CKRecord( + recordID: CKRecord.ID(1:modelCs/external.zone/external.owner), + recordType: "modelCs", + parent: CKReference(recordID: CKRecord.ID(1:modelBs/external.zone/external.owner)), + share: nil, + id: 1, + modelBID: 1, + title: "Blob" ) ] ) diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index 13410a6c..d7ef5e19 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -19,78 +19,6 @@ #""" [ [0]: """ - CREATE TRIGGER "after_delete_on_sqlitedata_icloud_metadata" - AFTER UPDATE OF "_isDeleted" ON "sqlitedata_icloud_metadata" - FOR EACH ROW WHEN ((NOT ("old"."_isDeleted") AND "new"."_isDeleted") AND NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"())) BEGIN - SELECT "sqlitedata_icloud_didDelete"("new"."recordName", coalesce("new"."lastKnownServerRecord", ( - WITH "ancestorMetadatas" AS ( - SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName", "sqlitedata_icloud_metadata"."lastKnownServerRecord" AS "lastKnownServerRecord" - FROM "sqlitedata_icloud_metadata" - WHERE ("sqlitedata_icloud_metadata"."recordName" = "new"."recordName") - UNION ALL - SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName", "sqlitedata_icloud_metadata"."lastKnownServerRecord" AS "lastKnownServerRecord" - FROM "sqlitedata_icloud_metadata" - JOIN "ancestorMetadatas" ON ("sqlitedata_icloud_metadata"."recordName" IS "ancestorMetadatas"."parentRecordName") - ) - SELECT "ancestorMetadatas"."lastKnownServerRecord" - FROM "ancestorMetadatas" - WHERE ("ancestorMetadatas"."parentRecordName" IS NULL) - )), "new"."share"); - END - """, - [1]: """ - CREATE TRIGGER "after_insert_on_sqlitedata_icloud_metadata" - AFTER INSERT ON "sqlitedata_icloud_metadata" - FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN - SELECT RAISE(ABORT, 'co.pointfree.SQLiteData.CloudKit.invalid-record-name-error') - WHERE NOT (((substr("new"."recordName", 1, 1) <> '_') AND (octet_length("new"."recordName") <= 255)) AND (octet_length("new"."recordName") = length("new"."recordName"))); - SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", coalesce("new"."lastKnownServerRecord", ( - WITH "ancestorMetadatas" AS ( - SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName", "sqlitedata_icloud_metadata"."lastKnownServerRecord" AS "lastKnownServerRecord" - FROM "sqlitedata_icloud_metadata" - WHERE ("sqlitedata_icloud_metadata"."recordName" = "new"."recordName") - UNION ALL - SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName", "sqlitedata_icloud_metadata"."lastKnownServerRecord" AS "lastKnownServerRecord" - FROM "sqlitedata_icloud_metadata" - JOIN "ancestorMetadatas" ON ("sqlitedata_icloud_metadata"."recordName" IS "ancestorMetadatas"."parentRecordName") - ) - SELECT "ancestorMetadatas"."lastKnownServerRecord" - FROM "ancestorMetadatas" - WHERE ("ancestorMetadatas"."parentRecordName" IS NULL) - )), ( - SELECT "sqlitedata_icloud_metadata"."lastKnownServerRecord" - FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" IS "new"."parentRecordPrimaryKey") AND ("sqlitedata_icloud_metadata"."recordType" IS "new"."parentRecordType")) - ), "new"."parentRecordPrimaryKey", "new"."parentRecordType"); - END - """, - [2]: """ - CREATE TRIGGER "after_update_on_sqlitedata_icloud_metadata" - AFTER UPDATE ON "sqlitedata_icloud_metadata" - FOR EACH ROW WHEN (("old"."_isDeleted" = "new"."_isDeleted") AND NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"())) BEGIN - SELECT RAISE(ABORT, 'co.pointfree.SQLiteData.CloudKit.invalid-record-name-error') - WHERE NOT (((substr("new"."recordName", 1, 1) <> '_') AND (octet_length("new"."recordName") <= 255)) AND (octet_length("new"."recordName") = length("new"."recordName"))); - SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", coalesce("new"."lastKnownServerRecord", ( - WITH "ancestorMetadatas" AS ( - SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName", "sqlitedata_icloud_metadata"."lastKnownServerRecord" AS "lastKnownServerRecord" - FROM "sqlitedata_icloud_metadata" - WHERE ("sqlitedata_icloud_metadata"."recordName" = "new"."recordName") - UNION ALL - SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName", "sqlitedata_icloud_metadata"."lastKnownServerRecord" AS "lastKnownServerRecord" - FROM "sqlitedata_icloud_metadata" - JOIN "ancestorMetadatas" ON ("sqlitedata_icloud_metadata"."recordName" IS "ancestorMetadatas"."parentRecordName") - ) - SELECT "ancestorMetadatas"."lastKnownServerRecord" - FROM "ancestorMetadatas" - WHERE ("ancestorMetadatas"."parentRecordName" IS NULL) - )), ( - SELECT "sqlitedata_icloud_metadata"."lastKnownServerRecord" - FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" IS "new"."parentRecordPrimaryKey") AND ("sqlitedata_icloud_metadata"."recordType" IS "new"."parentRecordType")) - ), "new"."parentRecordPrimaryKey", "new"."parentRecordType"); - END - """, - [3]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_childWithOnDeleteSetDefaults_from_sync_engine" AFTER DELETE ON "childWithOnDeleteSetDefaults" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -98,7 +26,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'childWithOnDeleteSetDefaults')); END """, - [4]: """ + [1]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_childWithOnDeleteSetDefaults_from_user" AFTER DELETE ON "childWithOnDeleteSetDefaults" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -119,7 +47,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'childWithOnDeleteSetDefaults')); END """, - [5]: """ + [2]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_childWithOnDeleteSetNulls_from_sync_engine" AFTER DELETE ON "childWithOnDeleteSetNulls" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -127,7 +55,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'childWithOnDeleteSetNulls')); END """, - [6]: """ + [3]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_childWithOnDeleteSetNulls_from_user" AFTER DELETE ON "childWithOnDeleteSetNulls" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -148,7 +76,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'childWithOnDeleteSetNulls')); END """, - [7]: """ + [4]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_modelAs_from_sync_engine" AFTER DELETE ON "modelAs" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -156,7 +84,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')); END """, - [8]: """ + [5]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_modelAs_from_user" AFTER DELETE ON "modelAs" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -177,7 +105,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')); END """, - [9]: """ + [6]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_modelBs_from_sync_engine" AFTER DELETE ON "modelBs" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -185,7 +113,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')); END """, - [10]: """ + [7]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_modelBs_from_user" AFTER DELETE ON "modelBs" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -206,7 +134,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')); END """, - [11]: """ + [8]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_modelCs_from_sync_engine" AFTER DELETE ON "modelCs" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -214,7 +142,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelCs')); END """, - [12]: """ + [9]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_modelCs_from_user" AFTER DELETE ON "modelCs" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -235,7 +163,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelCs')); END """, - [13]: """ + [10]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_parents_from_sync_engine" AFTER DELETE ON "parents" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -243,7 +171,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')); END """, - [14]: """ + [11]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_parents_from_user" AFTER DELETE ON "parents" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -264,7 +192,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')); END """, - [15]: """ + [12]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_reminderTags_from_sync_engine" AFTER DELETE ON "reminderTags" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -272,7 +200,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'reminderTags')); END """, - [16]: """ + [13]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_reminderTags_from_user" AFTER DELETE ON "reminderTags" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -293,7 +221,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'reminderTags')); END """, - [17]: """ + [14]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_remindersListAssets_from_sync_engine" AFTER DELETE ON "remindersListAssets" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -301,7 +229,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersListAssets')); END """, - [18]: """ + [15]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_remindersListAssets_from_user" AFTER DELETE ON "remindersListAssets" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -322,7 +250,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersListAssets')); END """, - [19]: """ + [16]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_remindersListPrivates_from_sync_engine" AFTER DELETE ON "remindersListPrivates" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -330,7 +258,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersListPrivates')); END """, - [20]: """ + [17]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_remindersListPrivates_from_user" AFTER DELETE ON "remindersListPrivates" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -351,7 +279,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersListPrivates')); END """, - [21]: """ + [18]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_remindersLists_from_sync_engine" AFTER DELETE ON "remindersLists" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -359,7 +287,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')); END """, - [22]: """ + [19]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_remindersLists_from_user" AFTER DELETE ON "remindersLists" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -380,7 +308,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')); END """, - [23]: """ + [20]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_reminders_from_sync_engine" AFTER DELETE ON "reminders" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -388,7 +316,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'reminders')); END """, - [24]: """ + [21]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_reminders_from_user" AFTER DELETE ON "reminders" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -409,7 +337,27 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'reminders')); END """, - [25]: """ + [22]: """ + CREATE TRIGGER "sqlitedata_icloud_after_delete_on_sqlitedata_icloud_metadata" + AFTER UPDATE OF "_isDeleted" ON "sqlitedata_icloud_metadata" + FOR EACH ROW WHEN ((NOT ("old"."_isDeleted") AND "new"."_isDeleted") AND NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"())) BEGIN + SELECT "sqlitedata_icloud_didDelete"("new"."recordName", coalesce("new"."lastKnownServerRecord", ( + WITH "ancestorMetadatas" AS ( + SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName", "sqlitedata_icloud_metadata"."lastKnownServerRecord" AS "lastKnownServerRecord" + FROM "sqlitedata_icloud_metadata" + WHERE ("sqlitedata_icloud_metadata"."recordName" = "new"."recordName") + UNION ALL + SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName", "sqlitedata_icloud_metadata"."lastKnownServerRecord" AS "lastKnownServerRecord" + FROM "sqlitedata_icloud_metadata" + JOIN "ancestorMetadatas" ON ("sqlitedata_icloud_metadata"."recordName" IS "ancestorMetadatas"."parentRecordName") + ) + SELECT "ancestorMetadatas"."lastKnownServerRecord" + FROM "ancestorMetadatas" + WHERE ("ancestorMetadatas"."parentRecordName" IS NULL) + )), "new"."share"); + END + """, + [23]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_tags_from_sync_engine" AFTER DELETE ON "tags" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -417,7 +365,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."title") AND ("sqlitedata_icloud_metadata"."recordType" = 'tags')); END """, - [26]: """ + [24]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_tags_from_user" AFTER DELETE ON "tags" FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN @@ -438,7 +386,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."title") AND ("sqlitedata_icloud_metadata"."recordType" = 'tags')); END """, - [27]: """ + [25]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_childWithOnDeleteSetDefaults" AFTER INSERT ON "childWithOnDeleteSetDefaults" FOR EACH ROW BEGIN @@ -456,16 +404,15 @@ 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", 'childWithOnDeleteSetDefaults', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'childWithOnDeleteSetDefaults', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), 'zone'), coalesce((SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), '__defaultOwner__'), "new"."parentID", 'parents' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), '__defaultOwner__'), "new"."parentID", 'parents' + ON CONFLICT DO NOTHING; END """, - [28]: """ + [26]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_childWithOnDeleteSetNulls" AFTER INSERT ON "childWithOnDeleteSetNulls" FOR EACH ROW BEGIN @@ -483,16 +430,15 @@ 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", 'childWithOnDeleteSetNulls', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'childWithOnDeleteSetNulls', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), 'zone'), coalesce((SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), '__defaultOwner__'), "new"."parentID", 'parents' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), '__defaultOwner__'), "new"."parentID", 'parents' + ON CONFLICT DO NOTHING; END """, - [29]: """ + [27]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_modelAs" AFTER INSERT ON "modelAs" FOR EACH ROW BEGIN @@ -510,12 +456,11 @@ 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", 'modelAs', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."id", 'modelAs', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; END """, - [30]: """ + [28]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_modelBs" AFTER INSERT ON "modelBs" FOR EACH ROW BEGIN @@ -533,16 +478,15 @@ 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", 'modelBs', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'modelBs', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs'))), 'zone'), coalesce((SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs'))), '__defaultOwner__'), "new"."modelAID", 'modelAs' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')))), '__defaultOwner__'), "new"."modelAID", 'modelAs' + ON CONFLICT DO NOTHING; END """, - [31]: """ + [29]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_modelCs" AFTER INSERT ON "modelCs" FOR EACH ROW BEGIN @@ -560,16 +504,15 @@ 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", 'modelCs', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'modelCs', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs'))), 'zone'), coalesce((SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs'))), '__defaultOwner__'), "new"."modelBID", 'modelBs' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')))), '__defaultOwner__'), "new"."modelBID", 'modelBs' + ON CONFLICT DO NOTHING; END """, - [32]: """ + [30]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_parents" AFTER INSERT ON "parents" FOR EACH ROW BEGIN @@ -587,12 +530,11 @@ 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", 'parents', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."id", 'parents', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; END """, - [33]: """ + [31]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_reminderTags" AFTER INSERT ON "reminderTags" FOR EACH ROW BEGIN @@ -610,12 +552,11 @@ 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", 'reminderTags', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."id", 'reminderTags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; END """, - [34]: """ + [32]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_reminders" AFTER INSERT ON "reminders" FOR EACH ROW BEGIN @@ -633,16 +574,15 @@ 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", 'reminders', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'reminders', 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((SELECT "sqlitedata_icloud_metadata"."ownerName" + 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" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' + ON CONFLICT DO NOTHING; END """, - [35]: """ + [33]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_remindersListAssets" AFTER INSERT ON "remindersListAssets" FOR EACH ROW BEGIN @@ -660,16 +600,15 @@ 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", 'remindersListAssets', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'remindersListAssets', 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((SELECT "sqlitedata_icloud_metadata"."ownerName" + 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" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' + ON CONFLICT DO NOTHING; END """, - [36]: """ + [34]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_remindersListPrivates" AFTER INSERT ON "remindersListPrivates" FOR EACH ROW BEGIN @@ -687,16 +626,15 @@ 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((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", '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((SELECT "sqlitedata_icloud_metadata"."ownerName" + 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" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' + ON CONFLICT DO NOTHING; END """, - [37]: """ + [35]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_remindersLists" AFTER INSERT ON "remindersLists" FOR EACH ROW BEGIN @@ -714,12 +652,20 @@ 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", 'remindersLists', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."id", 'remindersLists', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; END """, - [38]: """ + [36]: """ + CREATE TRIGGER "sqlitedata_icloud_after_insert_on_sqlitedata_icloud_metadata" + AFTER INSERT ON "sqlitedata_icloud_metadata" + FOR EACH ROW WHEN NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) BEGIN + SELECT RAISE(ABORT, 'co.pointfree.SQLiteData.CloudKit.invalid-record-name-error') + WHERE NOT (((substr("new"."recordName", 1, 1) <> '_') AND (octet_length("new"."recordName") <= 255)) AND (octet_length("new"."recordName") = length("new"."recordName"))); + SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "new"."zoneName", "new"."ownerName", NULL); + END + """, + [37]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_tags" AFTER INSERT ON "tags" FOR EACH ROW BEGIN @@ -737,12 +683,11 @@ 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"."title", 'tags', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."title", 'tags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; END """, - [39]: """ + [38]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_childWithOnDeleteSetDefaults" AFTER UPDATE OF "id" ON "childWithOnDeleteSetDefaults" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -763,7 +708,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'childWithOnDeleteSetDefaults')); END """, - [40]: """ + [39]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_childWithOnDeleteSetNulls" AFTER UPDATE OF "id" ON "childWithOnDeleteSetNulls" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -784,7 +729,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'childWithOnDeleteSetNulls')); END """, - [41]: """ + [40]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_modelAs" AFTER UPDATE OF "id" ON "modelAs" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -805,7 +750,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')); END """, - [42]: """ + [41]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_modelBs" AFTER UPDATE OF "id" ON "modelBs" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -826,7 +771,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')); END """, - [43]: """ + [42]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_modelCs" AFTER UPDATE OF "id" ON "modelCs" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -847,7 +792,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelCs')); END """, - [44]: """ + [43]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_parents" AFTER UPDATE OF "id" ON "parents" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -868,7 +813,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')); END """, - [45]: """ + [44]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_reminderTags" AFTER UPDATE OF "id" ON "reminderTags" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -889,7 +834,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'reminderTags')); END """, - [46]: """ + [45]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_reminders" AFTER UPDATE OF "id" ON "reminders" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -910,7 +855,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'reminders')); END """, - [47]: """ + [46]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_remindersListAssets" AFTER UPDATE OF "id" ON "remindersListAssets" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -931,7 +876,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersListAssets')); END """, - [48]: """ + [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 @@ -952,7 +897,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersListPrivates')); END """, - [49]: """ + [48]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_remindersLists" AFTER UPDATE OF "id" ON "remindersLists" FOR EACH ROW WHEN ("old"."id" <> "new"."id") BEGIN @@ -973,7 +918,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')); END """, - [50]: """ + [49]: """ CREATE TRIGGER "sqlitedata_icloud_after_primary_key_change_on_tags" AFTER UPDATE OF "title" ON "tags" FOR EACH ROW WHEN ("old"."title" <> "new"."title") BEGIN @@ -994,7 +939,7 @@ WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "old"."title") AND ("sqlitedata_icloud_metadata"."recordType" = 'tags')); END """, - [51]: """ + [50]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_childWithOnDeleteSetDefaults" AFTER UPDATE ON "childWithOnDeleteSetDefaults" FOR EACH ROW BEGIN @@ -1012,16 +957,22 @@ 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", 'childWithOnDeleteSetDefaults', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'childWithOnDeleteSetDefaults', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), 'zone'), coalesce((SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), '__defaultOwner__'), "new"."parentID", 'parents' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), '__defaultOwner__'), "new"."parentID", 'parents' + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + FROM "sqlitedata_icloud_metadata" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), "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"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = "new"."parentID", "parentRecordType" = 'parents', "userModificationTime" = "sqlitedata_icloud_currentTime"() + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'childWithOnDeleteSetDefaults')); END """, - [52]: """ + [51]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_childWithOnDeleteSetNulls" AFTER UPDATE ON "childWithOnDeleteSetNulls" FOR EACH ROW BEGIN @@ -1039,16 +990,22 @@ 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", 'childWithOnDeleteSetNulls', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'childWithOnDeleteSetNulls', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + FROM "sqlitedata_icloud_metadata" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + FROM "sqlitedata_icloud_metadata" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), '__defaultOwner__'), "new"."parentID", 'parents' + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), 'zone'), coalesce((SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), "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"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), '__defaultOwner__'), "new"."parentID", 'parents' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')))), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = "new"."parentID", "parentRecordType" = 'parents', "userModificationTime" = "sqlitedata_icloud_currentTime"() + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'childWithOnDeleteSetNulls')); END """, - [53]: """ + [52]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_modelAs" AFTER UPDATE ON "modelAs" FOR EACH ROW BEGIN @@ -1066,12 +1023,14 @@ 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", 'modelAs', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."id", 'modelAs', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = coalesce("sqlitedata_icloud_currentZoneName"(), "sqlitedata_icloud_metadata"."zoneName"), "ownerName" = coalesce("sqlitedata_icloud_currentOwnerName"(), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = NULL, "parentRecordType" = NULL, "userModificationTime" = "sqlitedata_icloud_currentTime"() + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')); END """, - [54]: """ + [53]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_modelBs" AFTER UPDATE ON "modelBs" FOR EACH ROW BEGIN @@ -1089,16 +1048,22 @@ 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", 'modelBs', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'modelBs', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + FROM "sqlitedata_icloud_metadata" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + FROM "sqlitedata_icloud_metadata" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')))), '__defaultOwner__'), "new"."modelAID", 'modelAs' + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs'))), 'zone'), coalesce((SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')))), "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"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs'))), '__defaultOwner__'), "new"."modelAID", 'modelAs' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs')))), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = "new"."modelAID", "parentRecordType" = 'modelAs', "userModificationTime" = "sqlitedata_icloud_currentTime"() + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')); END """, - [55]: """ + [54]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_modelCs" AFTER UPDATE ON "modelCs" FOR EACH ROW BEGIN @@ -1116,16 +1081,22 @@ 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", 'modelCs', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'modelCs', coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs'))), 'zone'), coalesce((SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')))), 'zone'), coalesce(coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + FROM "sqlitedata_icloud_metadata" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')))), '__defaultOwner__'), "new"."modelBID", 'modelBs' + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = coalesce(coalesce("sqlitedata_icloud_currentZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" FROM "sqlitedata_icloud_metadata" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs'))), '__defaultOwner__'), "new"."modelBID", 'modelBs' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')))), "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"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs')))), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = "new"."modelBID", "parentRecordType" = 'modelBs', "userModificationTime" = "sqlitedata_icloud_currentTime"() + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelCs')); END """, - [56]: """ + [55]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_parents" AFTER UPDATE ON "parents" FOR EACH ROW BEGIN @@ -1143,12 +1114,14 @@ 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", 'parents', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."id", 'parents', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = coalesce("sqlitedata_icloud_currentZoneName"(), "sqlitedata_icloud_metadata"."zoneName"), "ownerName" = coalesce("sqlitedata_icloud_currentOwnerName"(), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = NULL, "parentRecordType" = NULL, "userModificationTime" = "sqlitedata_icloud_currentTime"() + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents')); END """, - [57]: """ + [56]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_reminderTags" AFTER UPDATE ON "reminderTags" FOR EACH ROW BEGIN @@ -1166,12 +1139,14 @@ 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", 'reminderTags', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."id", 'reminderTags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = coalesce("sqlitedata_icloud_currentZoneName"(), "sqlitedata_icloud_metadata"."zoneName"), "ownerName" = coalesce("sqlitedata_icloud_currentOwnerName"(), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = NULL, "parentRecordType" = NULL, "userModificationTime" = "sqlitedata_icloud_currentTime"() + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'reminderTags')); END """, - [58]: """ + [57]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_reminders" AFTER UPDATE ON "reminders" FOR EACH ROW BEGIN @@ -1189,16 +1164,22 @@ 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", 'reminders', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'reminders', 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" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = 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((SELECT "sqlitedata_icloud_metadata"."ownerName" + 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'))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + 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" = 'reminders')); END """, - [59]: """ + [58]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_remindersListAssets" AFTER UPDATE ON "remindersListAssets" FOR EACH ROW BEGIN @@ -1216,16 +1197,22 @@ 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", 'remindersListAssets', coalesce((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'remindersListAssets', 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((SELECT "sqlitedata_icloud_metadata"."ownerName" + 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" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = 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')))), "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" = 'remindersListAssets')); END """, - [60]: """ + [59]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_remindersListPrivates" AFTER UPDATE ON "remindersListPrivates" FOR EACH ROW BEGIN @@ -1243,16 +1230,22 @@ 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((SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", '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((SELECT "sqlitedata_icloud_metadata"."ownerName" + 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" - WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = 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')))), "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')); END """, - [61]: """ + [60]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_remindersLists" AFTER UPDATE ON "remindersLists" FOR EACH ROW BEGIN @@ -1270,9 +1263,33 @@ 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", 'remindersLists', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."id", 'remindersLists', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = coalesce("sqlitedata_icloud_currentZoneName"(), "sqlitedata_icloud_metadata"."zoneName"), "ownerName" = coalesce("sqlitedata_icloud_currentOwnerName"(), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = NULL, "parentRecordType" = NULL, "userModificationTime" = "sqlitedata_icloud_currentTime"() + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."id") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists')); + END + """, + [61]: """ + CREATE TRIGGER "sqlitedata_icloud_after_update_on_sqlitedata_icloud_metadata" + AFTER UPDATE ON "sqlitedata_icloud_metadata" + FOR EACH ROW WHEN (("old"."_isDeleted" = "new"."_isDeleted") AND NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"())) BEGIN + SELECT RAISE(ABORT, 'co.pointfree.SQLiteData.CloudKit.invalid-record-name-error') + WHERE NOT (((substr("new"."recordName", 1, 1) <> '_') AND (octet_length("new"."recordName") <= 255)) AND (octet_length("new"."recordName") = length("new"."recordName"))); + SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "old"."zoneName", "old"."ownerName", CASE WHEN (("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")) THEN ( + WITH "descendantMetadatas" AS ( + SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", NULL AS "parentRecordName" + FROM "sqlitedata_icloud_metadata" + WHERE ("sqlitedata_icloud_metadata"."recordName" = "new"."recordName") + UNION ALL + SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName" + FROM "sqlitedata_icloud_metadata" + JOIN "descendantMetadatas" ON ("sqlitedata_icloud_metadata"."parentRecordName" = "descendantMetadatas"."recordName") + ) + SELECT json_group_array("descendantMetadatas"."recordName") + FROM "descendantMetadatas" + WHERE ("descendantMetadatas"."recordName" <> "new"."recordName") + ) END); END """, [62]: """ @@ -1293,9 +1310,30 @@ 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"."title", 'tags', 'zone', '__defaultOwner__', NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + SELECT "new"."title", 'tags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL + ON CONFLICT DO NOTHING; + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = coalesce("sqlitedata_icloud_currentZoneName"(), "sqlitedata_icloud_metadata"."zoneName"), "ownerName" = coalesce("sqlitedata_icloud_currentOwnerName"(), "sqlitedata_icloud_metadata"."ownerName"), "parentRecordPrimaryKey" = NULL, "parentRecordType" = NULL, "userModificationTime" = "sqlitedata_icloud_currentTime"() + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."title") AND ("sqlitedata_icloud_metadata"."recordType" = 'tags')); + END + """, + [63]: """ + CREATE TRIGGER "sqlitedata_icloud_after_zone_update_on_sqlitedata_icloud_metadata" + AFTER UPDATE OF "zoneName", "ownerName" ON "sqlitedata_icloud_metadata" + FOR EACH ROW WHEN (("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")) BEGIN + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = "new"."zoneName", "ownerName" = "new"."ownerName", "lastKnownServerRecord" = NULL, "_lastKnownServerRecordAllFields" = NULL + WHERE ("sqlitedata_icloud_metadata"."recordName" IN (WITH "descendantMetadatas" AS ( + SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", NULL AS "parentRecordName" + FROM "sqlitedata_icloud_metadata" + WHERE ("sqlitedata_icloud_metadata"."recordName" = "new"."recordName") + UNION ALL + SELECT "sqlitedata_icloud_metadata"."recordName" AS "recordName", "sqlitedata_icloud_metadata"."parentRecordName" AS "parentRecordName" + FROM "sqlitedata_icloud_metadata" + JOIN "descendantMetadatas" ON ("sqlitedata_icloud_metadata"."parentRecordName" = "descendantMetadatas"."recordName") + ) + SELECT "descendantMetadatas"."recordName" + FROM "descendantMetadatas")); END """ ] diff --git a/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift b/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift index 5e34de4e..ba37e57b 100644 --- a/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift +++ b/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift @@ -7,7 +7,7 @@ import Testing import os @Suite( - .snapshots(record: .missing), + .snapshots(record: .failed), .dependencies { $0.currentTime.now = 0 $0.dataManager = InMemoryDataManager() diff --git a/Tests/SQLiteDataTests/Internal/PrintTimestampsScope.swift b/Tests/SQLiteDataTests/Internal/PrintTimestampsScope.swift index f300124c..067815c6 100644 --- a/Tests/SQLiteDataTests/Internal/PrintTimestampsScope.swift +++ b/Tests/SQLiteDataTests/Internal/PrintTimestampsScope.swift @@ -20,9 +20,9 @@ } extension Trait where Self == _PrintTimestampsScope { - static var printTimestamps: Self { .init() } + static var printTimestamps: Self { Self() } static func printTimestamps(_ printTimestamps: Bool) -> Self { - .init(printTimestamps) + Self(printTimestamps) } } #endif diff --git a/Tests/SQLiteDataTests/Internal/Schema.swift b/Tests/SQLiteDataTests/Internal/Schema.swift index 92614e21..3d11f0a7 100644 --- a/Tests/SQLiteDataTests/Internal/Schema.swift +++ b/Tests/SQLiteDataTests/Internal/Schema.swift @@ -82,6 +82,9 @@ func database( if attachMetadatabase { try db.attachMetadatabase(containerIdentifier: containerIdentifier) } + // db.trace { + // print($0.expandedDescription) + // } } let url = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).sqlite") let database = try DatabasePool(path: url.path(), configuration: configuration) diff --git a/Tests/SQLiteDataTests/Internal/TestScopes.swift b/Tests/SQLiteDataTests/Internal/TestScopes.swift index 42ab482d..3608458b 100644 --- a/Tests/SQLiteDataTests/Internal/TestScopes.swift +++ b/Tests/SQLiteDataTests/Internal/TestScopes.swift @@ -25,7 +25,7 @@ static func prepareDatabase( _ prepareDatabase: @escaping @Sendable (UserDatabase) async throws -> Void ) -> Self { - .init(prepareDatabase: prepareDatabase) + Self(prepareDatabase: prepareDatabase) } } @@ -48,7 +48,7 @@ extension Trait where Self == _StartImmediatelyTrait { static func startImmediately(_ startImmediately: Bool) -> Self { - .init(startImmediately: startImmediately) + Self(startImmediately: startImmediately) } } @@ -70,9 +70,9 @@ } extension Trait where Self == _AttachMetadatabaseTrait { - static var attachMetadatabase: Self { .init(attachMetadatabase: true) } + static var attachMetadatabase: Self { Self(attachMetadatabase: true) } static func attachMetadatabase(_ attachMetadatabase: Bool) -> Self { - .init(attachMetadatabase: attachMetadatabase) + Self(attachMetadatabase: attachMetadatabase) } } @@ -96,9 +96,9 @@ } extension Trait where Self == _AccountStatusScope { - static var accountStatus: Self { .init() } + static var accountStatus: Self { Self() } static func accountStatus(_ accountStatus: CKAccountStatus) -> Self { - .init(accountStatus) + Self(accountStatus) } } #endif