From 04d59f4af371fa349b97727381fcba1a89e57e85 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 17:28:24 -0500 Subject: [PATCH 01/25] wip --- .../CloudKit/Internal/Triggers.swift | 76 +-- Sources/SQLiteData/CloudKit/SyncEngine.swift | 207 ++++--- .../SQLiteData/CloudKit/SyncMetadata.swift | 18 + .../CloudKitTests/AccountLifecycleTests.swift | 206 +++---- .../CloudKitTests/AppLifecycleTests.swift | 10 - .../CloudKitTests/SharingTests.swift | 570 +++++++++--------- .../SyncEngineLifecycleTests.swift | 109 ++-- .../CloudKitTests/TriggerTests.swift | 48 +- .../Internal/BaseCloudKitTests.swift | 2 +- Tests/SQLiteDataTests/Internal/Schema.swift | 3 + 10 files changed, 597 insertions(+), 652 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index 24ec4200..7ad7b497 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -164,6 +164,8 @@ } onConflict: { ($0.recordPrimaryKey, $0.recordType) } doUpdate: { + $0.zoneName = $1.zoneName + $0.ownerName = $1.ownerName $0.parentRecordPrimaryKey = $1.parentRecordPrimaryKey $0.parentRecordType = $1.parentRecordType $0.userModificationTime = $1.userModificationTime @@ -193,14 +195,18 @@ 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 +// lastKnownServerRecord: new.lastKnownServerRecord +// ?? rootServerRecord(recordName: new.recordName), +// newParentLastKnownServerRecord: parentLastKnownServerRecord( +// parentRecordPrimaryKey: new.parentRecordPrimaryKey, +// parentRecordType: new.parentRecordType +// ), +// parentRecordPrimaryKey: new.parentRecordPrimaryKey, +// parentRecordType: new.parentRecordType ) ) } when: { _ in @@ -214,19 +220,23 @@ createTemporaryTrigger( "after_update_on_sqlitedata_icloud_metadata", ifNotExists: true, - after: .update { _, new in + after: .update { old, 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: old.zoneName, + oldOwnerName: old.ownerName +// lastKnownServerRecord: new.lastKnownServerRecord +// ?? rootServerRecord(recordName: new.recordName), +// newParentLastKnownServerRecord: parentLastKnownServerRecord( +// parentRecordPrimaryKey: new.parentRecordPrimaryKey, +// parentRecordType: new.parentRecordType +// ), +// parentRecordPrimaryKey: new.parentRecordPrimaryKey, +// parentRecordType: new.parentRecordType ) ) } when: { old, new in @@ -258,7 +268,7 @@ } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - private func parentFields( +private func parentFields( alias: StructuredQueriesCore.TableAlias.TableColumns, parentForeignKey: ForeignKey?, defaultZone: CKRecordZone @@ -268,15 +278,7 @@ 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 let parentRecordPrimaryKey = #sql( @@ -285,19 +287,25 @@ ) let parentRecordType = #sql("\(bind: foreignKey.table)", as: String.self) let parentMetadata = - SyncMetadata + SyncMetadata .where { $0.recordPrimaryKey.eq(parentRecordPrimaryKey) - && $0.recordType.eq(parentRecordType) + && $0.recordType.eq(parentRecordType) + } + let metadata = + SyncMetadata + .where { + $0.recordPrimaryKey.eq(#sql("\(alias.primaryKey)")) + && $0.recordType.eq(#sql("\(type(of: alias).QueryValue.self)")) } return ( parentRecordPrimaryKey, parentRecordType, - #sql("coalesce((\(parentMetadata.select(\.zoneName))), \(zoneName))"), - #sql("coalesce((\(parentMetadata.select(\.ownerName))), \(ownerName))") + #sql("coalesce((\(parentMetadata.select(\.zoneName))), '')"), + #sql("coalesce((\(parentMetadata.select(\.ownerName))), '')") ) } - ?? (nil, nil, zoneName, ownerName) + ?? (nil, nil, "", "") } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @@ -314,7 +322,7 @@ } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - private func checkWritePermissions( +private func checkWritePermissions( alias: StructuredQueriesCore.TableAlias.TableColumns, parentForeignKey: ForeignKey?, defaultZone: CKRecordZone @@ -378,7 +386,7 @@ } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - private func parentLastKnownServerRecordIfShared( + private func parentLastKnownServerRecord( parentRecordPrimaryKey: some QueryExpression, parentRecordType: some QueryExpression ) -> some QueryExpression { diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 2d23bcc0..70dde517 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -604,37 +604,65 @@ "sqlitedata_icloud_didUpdate", as: (( String, - CKRecord?.SystemFieldsRepresentation, - CKRecord?.SystemFieldsRepresentation, - String?, - String? + String, + String, + String, + String +// CKRecord?.SystemFieldsRepresentation, +// CKRecord?.SystemFieldsRepresentation, +// String?, +// String? ) -> Void).self ) func didUpdate( recordName: String, - lastKnownServerRecord: CKRecord?, - newParentLastKnownServerRecord: CKRecord?, - parentRecordPrimaryKey: String? = nil, - parentRecordType: String? = nil + zoneName: String, + ownerName: String, + oldZoneName: String, + oldOwnerName: 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. - """ - } + let zoneID = CKRecordZone.ID.init( + zoneName: oldZoneName, + ownerName: oldOwnerName + ) // lastKnownServerRecord?.recordID.zoneID ?? defaultZone.zoneID + let newZoneID = CKRecordZone.ID.init(zoneName: zoneName, ownerName: ownerName) + //newParentLastKnownServerRecord?.recordID.zoneID + if 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. + // """ + // } + // } + // throw ZoneChangingError(recordName: recordName, zoneID: zoneID, newZoneID: newZoneID) + let syncEngine = self.syncEngines.withValue { + zoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared + } + syncEngine?.state + .add(pendingRecordZoneChanges: [ + .deleteRecord(CKRecord.ID.init(recordName: recordName, zoneID: zoneID)) + ]) + + let newSyncEngine = self.syncEngines.withValue { + newZoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared } - throw ZoneChangingError(recordName: recordName, zoneID: zoneID, newZoneID: newZoneID) + newSyncEngine?.state + .add(pendingRecordZoneChanges: [ + .saveRecord(CKRecord.ID.init(recordName: recordName, zoneID: newZoneID)) + ]) + return } let change = CKSyncEngine.PendingRecordZoneChange.saveRecord( @@ -910,7 +938,7 @@ catching: { try await metadatabase.read { db in try SyncMetadata - .where { $0.recordName.eq(recordID.recordName) } + .find(recordID) .select { ($0, $0._lastKnownServerRecordAllFields) } .fetchOne(db) } @@ -998,7 +1026,7 @@ return nil } } - let deletedRecordNames = deletedRecordIDs.map(\.recordName) + //let deletedRecordNames = deletedRecordIDs.map(\.recordName) let (sharesToDelete, recordsWithRoot): ([CKShare?], [(lastKnownServerRecord: CKRecord?, rootLastKnownServerRecord: CKRecord?)]) = @@ -1006,14 +1034,16 @@ 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 +1069,8 @@ ) } query: { RecordWithRoot - .where { $0.recordName.in(deletedRecordNames) } + // TODO: look into this + .where { $0.recordName.in(deletedRecordIDs.map(\.recordName)) } .select { ($0.lastKnownServerRecord, $0.rootLastKnownServerRecord) } } .fetchAll(db) @@ -1069,7 +1100,7 @@ await withErrorReporting(.sqliteDataCloudKitFailure) { try await userDatabase.write { db in try SyncMetadata - .where { $0.recordName.in(deletedRecordNames) } + .findAll(deletedRecordIDs) .delete() .execute(db) } @@ -1359,7 +1390,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,7 +1544,7 @@ else { return } try await userDatabase.write { db in try SyncMetadata - .where { $0.recordName.eq(rootRecordID.recordName) } + .find(rootRecordID) .update { $0.share = share } .execute(db) } @@ -1521,16 +1552,18 @@ func deleteShare(recordID: CKRecord.ID) async throws { try await userDatabase.write { db in - let shareAndRecordName = + let shareAndLastKnownServerRecord = try SyncMetadata .where(\.isShared) - .select { ($0.share, $0.recordName) } + .select { ($0.share, $0.lastKnownServerRecord) } .fetchAll(db) .first(where: { share, _ in share?.recordID == recordID }) ?? nil - guard let (_, recordName) = shareAndRecordName + guard + let (_, lastKnownServerRecord) = shareAndLastKnownServerRecord, + let lastKnownServerRecord else { return } try SyncMetadata - .where { $0.recordName.eq(recordName) } + .find(lastKnownServerRecord.recordID) .update { $0.share = nil } .execute(db) } @@ -1553,36 +1586,40 @@ db: Database ) { withErrorReporting(.sqliteDataCloudKitFailure) { + guard let recordPrimaryKey = serverRecord.recordID.recordPrimaryKey + else { return } + + let metadata = 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: { + // TODO: set parent fields? + $0.setLastKnownServerRecord(serverRecord) + } + .returning(\.self) + .fetchOne(db) + 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: { - $0.setLastKnownServerRecord(serverRecord) - } - .execute(db) return } - let metadata = - try SyncMetadata - .where { $0.recordName.eq(serverRecord.recordID.recordName) } - .fetchOne(db) +// let metadata = +// try SyncMetadata +// .find(serverRecord.recordID) +// .fetchOne(db) serverRecord.userModificationTime = metadata?.userModificationTime ?? serverRecord.userModificationTime @@ -1601,11 +1638,11 @@ : nil ) } else { - reportIssue( - """ - Local database record could not be found for '\(serverRecord.recordID.recordName)'. - """ - ) +// reportIssue( +// """ +// Local database record could not be found for '\(serverRecord.recordID.recordName)'. +// """ +// ) } columnNames = _columnNames } else { @@ -1616,7 +1653,7 @@ 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 +1675,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( diff --git a/Sources/SQLiteData/CloudKit/SyncMetadata.swift b/Sources/SQLiteData/CloudKit/SyncMetadata.swift index c5e06857..0f655fd2 100644 --- a/Sources/SQLiteData/CloudKit/SyncMetadata.swift +++ b/Sources/SQLiteData/CloudKit/SyncMetadata.swift @@ -147,6 +147,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/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift index f8670788..dcb0cce3 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift @@ -379,60 +379,43 @@ assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { """ - ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ 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: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────┐ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "remindersLists", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:remindersLists", │ + │ parentRecordPrimaryKey: nil, │ + │ parentRecordType: nil, │ + │ parentRecordName: nil, │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + ├─────────────────────────────────────────┤ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "reminders", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:reminders", │ + │ parentRecordPrimaryKey: "1", │ + │ parentRecordType: "remindersLists", │ + │ parentRecordName: "1:remindersLists", │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + └─────────────────────────────────────────┘ """ } assertInlineSnapshot(of: container, as: .customDump) { @@ -471,74 +454,43 @@ assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { """ - ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ 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 │ - │ ) │ - └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────┐ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "remindersLists", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:remindersLists", │ + │ parentRecordPrimaryKey: nil, │ + │ parentRecordType: nil, │ + │ parentRecordName: nil, │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + ├─────────────────────────────────────────┤ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "reminders", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:reminders", │ + │ parentRecordPrimaryKey: "1", │ + │ parentRecordType: "remindersLists", │ + │ parentRecordName: "1:remindersLists", │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + └─────────────────────────────────────────┘ """ } assertInlineSnapshot(of: container, as: .customDump) { @@ -558,16 +510,6 @@ share: nil ), [1]: 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" - ), - [2]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, diff --git a/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift index 59033c08..2587d331 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift @@ -109,16 +109,6 @@ share: nil ), [1]: 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" - ), - [2]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index 67e7e714..8e511c0e 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -192,16 +192,6 @@ databaseScope: .shared, storage: [ [0]: 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" - ), - [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, @@ -286,43 +276,25 @@ assertQuery(SyncMetadata.order(by: \.recordName), database: syncEngine.metadatabase) { """ - ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ 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, │ - │ isCompleted: 0, │ - │ 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: "remindersLists", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:remindersLists", │ + │ parentRecordPrimaryKey: nil, │ + │ parentRecordType: nil, │ + │ parentRecordName: nil, │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + └─────────────────────────────────────────┘ """ } } @@ -359,99 +331,61 @@ try await syncEngine.processPendingRecordZoneChanges(scope: .shared) assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { """ - ┌─────────────────────────────────────────────────────────────────────────────────────────┐ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "modelAs", │ - │ zoneName: "external.zone", │ - │ ownerName: "external.owner", │ - │ recordName: "1:modelAs", │ - │ parentRecordPrimaryKey: nil, │ - │ parentRecordType: nil, │ - │ parentRecordName: nil, │ - │ lastKnownServerRecord: CKRecord( │ - │ recordID: CKRecord.ID(1:modelAs/external.zone/external.owner), │ - │ recordType: "modelAs", │ - │ parent: nil, │ - │ share: nil │ - │ ), │ - │ _lastKnownServerRecordAllFields: CKRecord( │ - │ recordID: CKRecord.ID(1:modelAs/external.zone/external.owner), │ - │ recordType: "modelAs", │ - │ parent: nil, │ - │ share: nil, │ - │ count: 0, │ - │ id: 1 │ - │ ), │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: true, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - ├─────────────────────────────────────────────────────────────────────────────────────────┤ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "modelBs", │ - │ zoneName: "external.zone", │ - │ ownerName: "external.owner", │ - │ recordName: "1:modelBs", │ - │ parentRecordPrimaryKey: "1", │ - │ parentRecordType: "modelAs", │ - │ parentRecordName: "1:modelAs", │ - │ lastKnownServerRecord: CKRecord( │ - │ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), │ - │ recordType: "modelBs", │ - │ parent: CKReference(recordID: CKRecord.ID(1: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(1:modelAs/external.zone/external.owner)), │ - │ share: nil, │ - │ id: 1, │ - │ isOn: 0, │ - │ modelAID: 1 │ - │ ), │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: true, │ - │ isShared: false, │ - │ userModificationTime: 60 │ - │ ) │ - ├─────────────────────────────────────────────────────────────────────────────────────────┤ - │ 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: "" │ - │ ), │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: true, │ - │ isShared: false, │ - │ userModificationTime: 60 │ - │ ) │ - └─────────────────────────────────────────────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────┐ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "modelAs", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:modelAs", │ + │ parentRecordPrimaryKey: nil, │ + │ parentRecordType: nil, │ + │ parentRecordName: nil, │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + ├─────────────────────────────────────────┤ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "modelBs", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:modelBs", │ + │ parentRecordPrimaryKey: "1", │ + │ parentRecordType: "modelAs", │ + │ parentRecordName: "1:modelAs", │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 60 │ + │ ) │ + ├─────────────────────────────────────────┤ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "modelCs", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:modelCs", │ + │ parentRecordPrimaryKey: "1", │ + │ parentRecordType: "modelBs", │ + │ parentRecordName: "1:modelBs", │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 60 │ + │ ) │ + └─────────────────────────────────────────┘ """ } assertInlineSnapshot(of: container, as: .customDump) { @@ -471,24 +405,6 @@ share: nil, count: 0, id: 1 - ), - [1]: CKRecord( - recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), - recordType: "modelBs", - parent: CKReference(recordID: CKRecord.ID(1:modelAs/external.zone/external.owner)), - share: nil, - id: 1, - isOn: 0, - modelAID: 1 - ), - [2]: 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: "" ) ] ) @@ -548,6 +464,16 @@ databaseScope: .shared, storage: [ [0]: 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" + ), + [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, @@ -751,14 +677,9 @@ assertQuery(SyncMetadata.select(\.share), database: syncEngine.metadatabase) { """ - ┌───────────────────────────────────────────────────────────────────────────────┐ - │ CKRecord( │ - │ recordID: CKRecord.ID(share-1:remindersLists/external.zone/external.owner), │ - │ recordType: "cloudkit.share", │ - │ parent: nil, │ - │ share: nil │ - │ ) │ - └───────────────────────────────────────────────────────────────────────────────┘ + ┌─────┐ + │ nil │ + └─────┘ """ } @@ -827,6 +748,45 @@ ) ) + assertQuery( + SyncMetadata.all, + database: syncEngine.metadatabase + ) { + """ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "remindersLists", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ 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: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: true, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ + """ + } + try await userDatabase.userWrite { db in try db.seed { Reminder(id: 1, title: "Get milk", remindersListID: 1) @@ -835,18 +795,59 @@ 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: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ 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: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: true, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + ├─────────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "reminders", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:reminders", │ + │ parentRecordPrimaryKey: "1", │ + │ parentRecordType: "remindersLists", │ + │ parentRecordName: "1:remindersLists", │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ """ } - assertInlineSnapshot(of: container, as: .customDump) { """ MockCloudContainer( @@ -864,16 +865,6 @@ share: nil ), [1]: 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" - ), - [2]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, @@ -980,6 +971,10 @@ database: syncEngine.metadatabase ) { """ + ┌────────────────────┬─────┐ + │ "1:remindersLists" │ nil │ + │ "1:reminders" │ nil │ + └────────────────────┴─────┘ """ } @@ -994,14 +989,10 @@ databaseScope: .shared, storage: [ [0]: 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" + recordID: CKRecord.ID(share-1:remindersLists/external.zone/external.owner), + recordType: "cloudkit.share", + parent: nil, + share: nil ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), @@ -1063,16 +1054,12 @@ ) ) - let error = await #expect(throws: DatabaseError.self) { - try await self.userDatabase.userWrite { db in - try ModelB.find(1).update { $0.modelAID = 2 }.execute(db) - } + try await self.userDatabase.userWrite { db in + try ModelB.find(1).update { $0.modelAID = 2 }.execute(db) } - #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. - """) + +// try await syncEngine.processPendingRecordZoneChanges(scope: .private) +// try await syncEngine.processPendingRecordZoneChanges(scope: .shared) assertQuery(ModelB.all, database: userDatabase.database) { """ @@ -1080,110 +1067,93 @@ │ ModelB( │ │ id: 1, │ │ isOn: true, │ - │ modelAID: 1 │ + │ modelAID: 2 │ │ ) │ └───────────────┘ """ } assertQuery(SyncMetadata.all, 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: "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", │ - │ 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: "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: "1", │ + │ recordType: "modelBs", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:modelBs", │ + │ parentRecordPrimaryKey: "2", │ + │ parentRecordType: "modelAs", │ + │ parentRecordName: "2: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: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "2:modelAs", │ + │ parentRecordPrimaryKey: nil, │ + │ parentRecordType: nil, │ + │ parentRecordName: nil, │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + └──────────────────────────────────────────────────────────────────────────────────┘ """ } assertInlineSnapshot(of: container, as: .customDump) { diff --git a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift index 3da22697..39d71707 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift @@ -273,56 +273,43 @@ try await Task.sleep(for: .seconds(1)) assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { """ - ┌───────────────────────────────────────────────────────────────────────────┐ - │ 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: nil │ - │ ), │ - │ _lastKnownServerRecordAllFields: CKRecord( │ - │ recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), │ - │ recordType: "remindersLists", │ - │ parent: nil, │ - │ share: nil, │ - │ id: 1, │ - │ isCompleted: 0, │ - │ title: "Personal" │ - │ ), │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: true, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - ├───────────────────────────────────────────────────────────────────────────┤ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "reminders", │ - │ zoneName: "external.zone", │ - │ ownerName: "external.owner", │ - │ recordName: "1:reminders", │ - │ parentRecordPrimaryKey: "1", │ - │ parentRecordType: "remindersLists", │ - │ parentRecordName: "1:remindersLists", │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 60 │ - │ ) │ - └───────────────────────────────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────┐ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "remindersLists", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:remindersLists", │ + │ parentRecordPrimaryKey: nil, │ + │ parentRecordType: nil, │ + │ parentRecordName: nil, │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + ├─────────────────────────────────────────┤ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "reminders", │ + │ zoneName: "zone", │ + │ ownerName: "__defaultOwner__", │ + │ recordName: "1:reminders", │ + │ parentRecordPrimaryKey: "1", │ + │ parentRecordType: "remindersLists", │ + │ parentRecordName: "1:remindersLists", │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 60 │ + │ ) │ + └─────────────────────────────────────────┘ """ } @@ -341,16 +328,6 @@ databaseScope: .shared, storage: [ [0]: 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" - ), - [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, @@ -406,7 +383,17 @@ ), sharedCloudDatabase: MockCloudDatabase( databaseScope: .shared, - storage: [] + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), + recordType: "remindersLists", + parent: nil, + share: nil, + id: 1, + isCompleted: 0, + title: "Personal" + ) + ] ) ) """ diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index 13410a6c..cb43317c 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -462,7 +462,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [28]: """ @@ -489,7 +489,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [29]: """ @@ -512,7 +512,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [30]: """ @@ -539,7 +539,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [31]: """ @@ -566,7 +566,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [32]: """ @@ -589,7 +589,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [33]: """ @@ -612,7 +612,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [34]: """ @@ -639,7 +639,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [35]: """ @@ -666,7 +666,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [36]: """ @@ -693,7 +693,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [37]: """ @@ -716,7 +716,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [38]: """ @@ -739,7 +739,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [39]: """ @@ -1018,7 +1018,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [52]: """ @@ -1045,7 +1045,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [53]: """ @@ -1068,7 +1068,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [54]: """ @@ -1095,7 +1095,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [55]: """ @@ -1122,7 +1122,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [56]: """ @@ -1145,7 +1145,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [57]: """ @@ -1168,7 +1168,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [58]: """ @@ -1195,7 +1195,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [59]: """ @@ -1222,7 +1222,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [60]: """ @@ -1249,7 +1249,7 @@ 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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [61]: """ @@ -1272,7 +1272,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [62]: """ @@ -1295,7 +1295,7 @@ ("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"; + DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; 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/Schema.swift b/Tests/SQLiteDataTests/Internal/Schema.swift index 92614e21..a7132784 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) From 9dd40a33bf4e07742dc3c419957440710dccba5d Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 18:07:19 -0500 Subject: [PATCH 02/25] wip --- .../CloudKit/Internal/Triggers.swift | 57 +- Sources/SQLiteData/CloudKit/SyncEngine.swift | 19 +- .../CloudKitTests/AccountLifecycleTests.swift | 206 ++++--- .../CloudKitTests/AppLifecycleTests.swift | 10 + .../CloudKitTests/CloudKitTests.swift | 12 +- .../FetchRecordZoneChangesTests.swift | 10 +- .../ForeignKeyConstraintTests.swift | 4 +- .../CloudKitTests/MergeConflictTests.swift | 29 +- .../SharingPermissionsTests.swift | 8 - .../CloudKitTests/SharingTests.swift | 550 ++++++++++-------- .../SyncEngineLifecycleTests.swift | 109 ++-- .../CloudKitTests/TriggerTests.swift | 114 ++-- Tests/SQLiteDataTests/Internal/Schema.swift | 6 +- 13 files changed, 644 insertions(+), 490 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index 7ad7b497..519e2a3d 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -220,8 +220,20 @@ createTemporaryTrigger( "after_update_on_sqlitedata_icloud_metadata", ifNotExists: true, - after: .update { old, new in + after: .update { + old, + new in validate(recordName: new.recordName) + SyncMetadata + .where { + $0.recordName.eq(new.recordName) + && $0.recordType.eq(new.recordType) + && (new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName)) + } + .update { + $0.lastKnownServerRecord = nil + $0._lastKnownServerRecordAllFields = nil + } Values( syncEngine.$didUpdate( recordName: new.recordName, @@ -229,14 +241,6 @@ ownerName: new.ownerName, oldZoneName: old.zoneName, oldOwnerName: old.ownerName -// lastKnownServerRecord: new.lastKnownServerRecord -// ?? rootServerRecord(recordName: new.recordName), -// newParentLastKnownServerRecord: parentLastKnownServerRecord( -// parentRecordPrimaryKey: new.parentRecordPrimaryKey, -// parentRecordType: new.parentRecordType -// ), -// parentRecordPrimaryKey: new.parentRecordPrimaryKey, -// parentRecordType: new.parentRecordType ) ) } when: { old, new in @@ -268,7 +272,7 @@ } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) -private func parentFields( + private func parentFields( alias: StructuredQueriesCore.TableAlias.TableColumns, parentForeignKey: ForeignKey?, defaultZone: CKRecordZone @@ -278,7 +282,15 @@ private func parentFields( 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 let parentRecordPrimaryKey = #sql( @@ -287,25 +299,24 @@ private func parentFields( ) let parentRecordType = #sql("\(bind: foreignKey.table)", as: String.self) let parentMetadata = - SyncMetadata + SyncMetadata .where { $0.recordPrimaryKey.eq(parentRecordPrimaryKey) - && $0.recordType.eq(parentRecordType) - } - let metadata = - SyncMetadata - .where { - $0.recordPrimaryKey.eq(#sql("\(alias.primaryKey)")) - && $0.recordType.eq(#sql("\(type(of: alias).QueryValue.self)")) + && $0.recordType.eq(parentRecordType) } return ( parentRecordPrimaryKey, parentRecordType, - #sql("coalesce((\(parentMetadata.select(\.zoneName))), '')"), - #sql("coalesce((\(parentMetadata.select(\.ownerName))), '')") + #sql("coalesce(\($defaultZoneName()), (\(parentMetadata.select(\.zoneName))), \(zoneName))"), + #sql("coalesce(\($defaultOwnerName()), (\(parentMetadata.select(\.ownerName))), \(ownerName))") ) } - ?? (nil, nil, "", "") + ?? ( + nil, + nil, + #sql("coalesce(\($defaultZoneName()), \(zoneName))"), + #sql("coalesce(\($defaultOwnerName()), \(ownerName))") + ) } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @@ -322,7 +333,7 @@ private func parentFields( } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) -private func checkWritePermissions( + private func checkWritePermissions( alias: StructuredQueriesCore.TableAlias.TableColumns, parentForeignKey: ForeignKey?, defaultZone: CKRecordZone diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 70dde517..bba89ca1 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: $defaultZoneName) + db.add(function: $defaultOwnerName) for trigger in SyncMetadata.callbackTriggers(for: self) { try trigger.execute(db) @@ -987,7 +989,7 @@ record.parent = CKRecord.Reference( recordID: CKRecord.ID( recordName: parentRecordName, - zoneID: record.recordID.zoneID + zoneID: recordID.zoneID ), action: .none ) @@ -1098,6 +1100,8 @@ } await withErrorReporting(.sqliteDataCloudKitFailure) { + guard !deletedRecordIDs.isEmpty + else { return } try await userDatabase.write { db in try SyncMetadata .findAll(deletedRecordIDs) @@ -1650,7 +1654,9 @@ } do { - try #sql(upsert(T.self, record: serverRecord, columnNames: columnNames)).execute(db) + try $_defaultZoneID.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 .find(serverRecord.recordID) @@ -2141,4 +2147,13 @@ } @TaskLocal package var _isSynchronizingChanges = false +@TaskLocal package var _defaultZoneID: CKRecordZone.ID? +@DatabaseFunction +func defaultZoneName() -> String? { + _defaultZoneID?.zoneName +} +@DatabaseFunction +func defaultOwnerName() -> String? { + _defaultZoneID?.ownerName +} #endif diff --git a/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift index dcb0cce3..f8670788 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift @@ -379,43 +379,60 @@ assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { """ - ┌─────────────────────────────────────────┐ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "remindersLists", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:remindersLists", │ - │ parentRecordPrimaryKey: nil, │ - │ parentRecordType: nil, │ - │ parentRecordName: nil, │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - ├─────────────────────────────────────────┤ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "reminders", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:reminders", │ - │ parentRecordPrimaryKey: "1", │ - │ parentRecordType: "remindersLists", │ - │ parentRecordName: "1:remindersLists", │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - └─────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ 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: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ """ } assertInlineSnapshot(of: container, as: .customDump) { @@ -454,43 +471,74 @@ assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { """ - ┌─────────────────────────────────────────┐ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "remindersLists", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:remindersLists", │ - │ parentRecordPrimaryKey: nil, │ - │ parentRecordType: nil, │ - │ parentRecordName: nil, │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - ├─────────────────────────────────────────┤ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "reminders", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:reminders", │ - │ parentRecordPrimaryKey: "1", │ - │ parentRecordType: "remindersLists", │ - │ parentRecordName: "1:remindersLists", │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - └─────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ 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) { @@ -510,6 +558,16 @@ share: nil ), [1]: 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" + ), + [2]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, diff --git a/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift index 2587d331..59033c08 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AppLifecycleTests.swift @@ -109,6 +109,16 @@ share: nil ), [1]: 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" + ), + [2]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, diff --git a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift index 57eb9b3c..b9587199 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift @@ -578,12 +578,12 @@ assertQuery(RemindersList.all, database: userDatabase.database) { """ - ┌─────────────────┐ - │ RemindersList( │ - │ id: 1, │ - │ title: "Work" │ - │ ) │ - └─────────────────┘ + ┌─────────────────────┐ + │ RemindersList( │ + │ id: 1, │ + │ title: "Personal" │ + │ ) │ + └─────────────────────┘ """ } assertQuery( diff --git a/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift b/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift index 6e6007af..7c4de2f0 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift @@ -152,7 +152,7 @@ │ isCompleted: true, │ │ priority: nil, │ │ title: "Get milk", │ - │ remindersListID: 2 │ + │ remindersListID: 1 │ │ ) │ └──────────────────────┘ """ @@ -163,7 +163,7 @@ ) { """ ┌────────────────────┬────────────────────┐ - │ "1:reminders" │ "2:remindersLists" │ + │ "1:reminders" │ "1:remindersLists" │ │ "1:remindersLists" │ nil │ │ "2:remindersLists" │ nil │ └────────────────────┴────────────────────┘ @@ -178,11 +178,11 @@ [0]: CKRecord( recordID: CKRecord.ID(1:reminders/zone/__defaultOwner__), recordType: "reminders", - parent: CKReference(recordID: CKRecord.ID(2:remindersLists/zone/__defaultOwner__)), + parent: CKReference(recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__)), share: nil, id: 1, isCompleted: 1, - remindersListID: 2, + remindersListID: 1, title: "Get milk" ), [1]: CKRecord( @@ -629,7 +629,7 @@ │ _isDeleted: false, │ │ hasLastKnownServerRecord: true, │ │ isShared: false, │ - │ userModificationTime: 0 │ + │ userModificationTime: -1 │ │ ) │ └────────────────────────────────────────────────────────────┘ """ diff --git a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift index 40688a42..04973165 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift @@ -509,7 +509,7 @@ ) { """ ┌────────────────────┬────────────────────┐ - │ "1:reminders" │ "2:remindersLists" │ + │ "1:reminders" │ "1:remindersLists" │ │ "1:remindersLists" │ nil │ │ "2:remindersLists" │ nil │ └────────────────────┴────────────────────┘ @@ -524,7 +524,7 @@ │ isCompleted: false, │ │ priority: nil, │ │ title: "Get milk", │ - │ remindersListID: 2 │ + │ remindersListID: 1 │ │ ) │ └───────────────────────┘ """ diff --git a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift index 645540a0..67207373 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift @@ -138,10 +138,10 @@ id: 1, id🗓️: 0, isCompleted: 1, - isCompleted🗓️: 30, + isCompleted🗓️: 60, remindersListID: 1, remindersListID🗓️: 0, - title: "Buy milk", + title: "", title🗓️: 60, 🗓️: 60 ), @@ -292,12 +292,12 @@ id: 1, id🗓️: 0, isCompleted: 1, - isCompleted🗓️: 60, + isCompleted🗓️: 30, remindersListID: 1, remindersListID🗓️: 0, - title: "Buy milk", + title: "", title🗓️: 30, - 🗓️: 60 + 🗓️: 30 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), @@ -361,12 +361,12 @@ id: 1, id🗓️: 0, isCompleted: 1, - isCompleted🗓️: 60, + isCompleted🗓️: 30, remindersListID: 1, remindersListID🗓️: 0, - title: "Buy milk", + title: "", title🗓️: 30, - 🗓️: 60 + 🗓️: 30 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), @@ -521,8 +521,8 @@ remindersListID: 1, remindersListID🗓️: 0, title: "Get milk", - title🗓️: 60, - 🗓️: 60 + title🗓️: 30, + 🗓️: 30 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), @@ -591,8 +591,8 @@ remindersListID: 1, remindersListID🗓️: 0, title: "Get milk", - title🗓️: 60, - 🗓️: 60 + title🗓️: 30, + 🗓️: 30 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), @@ -661,19 +661,18 @@ recordType: "reminders", parent: CKReference(recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__)), share: nil, - dueDate: Date(1970-01-01T00:00:30.000Z), dueDate🗓️: 1, 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/SharingPermissionsTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingPermissionsTests.swift index 2f867b2f..5ebcd451 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingPermissionsTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingPermissionsTests.swift @@ -450,14 +450,6 @@ recordType: "cloudkit.share", parent: nil, share: nil - ), - [1]: 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" ) ] ) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index 8e511c0e..d412adb3 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -192,6 +192,16 @@ databaseScope: .shared, storage: [ [0]: 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" + ), + [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, @@ -276,25 +286,43 @@ assertQuery(SyncMetadata.order(by: \.recordName), database: syncEngine.metadatabase) { """ - ┌─────────────────────────────────────────┐ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "remindersLists", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:remindersLists", │ - │ parentRecordPrimaryKey: nil, │ - │ parentRecordType: nil, │ - │ parentRecordName: nil, │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - └─────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ 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, │ + │ isCompleted: 0, │ + │ 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 │ + │ ) │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ """ } } @@ -331,61 +359,99 @@ try await syncEngine.processPendingRecordZoneChanges(scope: .shared) assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { """ - ┌─────────────────────────────────────────┐ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "modelAs", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:modelAs", │ - │ parentRecordPrimaryKey: nil, │ - │ parentRecordType: nil, │ - │ parentRecordName: nil, │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - ├─────────────────────────────────────────┤ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "modelBs", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:modelBs", │ - │ parentRecordPrimaryKey: "1", │ - │ parentRecordType: "modelAs", │ - │ parentRecordName: "1:modelAs", │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 60 │ - │ ) │ - ├─────────────────────────────────────────┤ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "modelCs", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:modelCs", │ - │ parentRecordPrimaryKey: "1", │ - │ parentRecordType: "modelBs", │ - │ parentRecordName: "1:modelBs", │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 60 │ - │ ) │ - └─────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────────────────────────────────────────────┐ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "modelAs", │ + │ zoneName: "external.zone", │ + │ ownerName: "external.owner", │ + │ recordName: "1:modelAs", │ + │ parentRecordPrimaryKey: nil, │ + │ parentRecordType: nil, │ + │ parentRecordName: nil, │ + │ lastKnownServerRecord: CKRecord( │ + │ recordID: CKRecord.ID(1:modelAs/external.zone/external.owner), │ + │ recordType: "modelAs", │ + │ parent: nil, │ + │ share: nil │ + │ ), │ + │ _lastKnownServerRecordAllFields: CKRecord( │ + │ recordID: CKRecord.ID(1:modelAs/external.zone/external.owner), │ + │ recordType: "modelAs", │ + │ parent: nil, │ + │ share: nil, │ + │ count: 0, │ + │ id: 1 │ + │ ), │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: true, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + ├─────────────────────────────────────────────────────────────────────────────────────────┤ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "modelBs", │ + │ zoneName: "external.zone", │ + │ ownerName: "external.owner", │ + │ recordName: "1:modelBs", │ + │ parentRecordPrimaryKey: "1", │ + │ parentRecordType: "modelAs", │ + │ parentRecordName: "1:modelAs", │ + │ lastKnownServerRecord: CKRecord( │ + │ recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), │ + │ recordType: "modelBs", │ + │ parent: CKReference(recordID: CKRecord.ID(1: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(1:modelAs/external.zone/external.owner)), │ + │ share: nil, │ + │ id: 1, │ + │ isOn: 0, │ + │ modelAID: 1 │ + │ ), │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: true, │ + │ isShared: false, │ + │ userModificationTime: 60 │ + │ ) │ + ├─────────────────────────────────────────────────────────────────────────────────────────┤ + │ 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: "" │ + │ ), │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: true, │ + │ isShared: false, │ + │ userModificationTime: 60 │ + │ ) │ + └─────────────────────────────────────────────────────────────────────────────────────────┘ """ } assertInlineSnapshot(of: container, as: .customDump) { @@ -405,6 +471,24 @@ share: nil, count: 0, id: 1 + ), + [1]: CKRecord( + recordID: CKRecord.ID(1:modelBs/external.zone/external.owner), + recordType: "modelBs", + parent: CKReference(recordID: CKRecord.ID(1:modelAs/external.zone/external.owner)), + share: nil, + id: 1, + isOn: 0, + modelAID: 1 + ), + [2]: 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: "" ) ] ) @@ -464,16 +548,6 @@ databaseScope: .shared, storage: [ [0]: 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" - ), - [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, @@ -677,9 +751,14 @@ assertQuery(SyncMetadata.select(\.share), database: syncEngine.metadatabase) { """ - ┌─────┐ - │ nil │ - └─────┘ + ┌───────────────────────────────────────────────────────────────────────────────┐ + │ CKRecord( │ + │ recordID: CKRecord.ID(share-1:remindersLists/external.zone/external.owner), │ + │ recordType: "cloudkit.share", │ + │ parent: nil, │ + │ share: nil │ + │ ) │ + └───────────────────────────────────────────────────────────────────────────────┘ """ } @@ -748,45 +827,6 @@ ) ) - assertQuery( - SyncMetadata.all, - database: syncEngine.metadatabase - ) { - """ - ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "remindersLists", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ 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: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: true, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ - """ - } - try await userDatabase.userWrite { db in try db.seed { Reminder(id: 1, title: "Get milk", remindersListID: 1) @@ -801,8 +841,8 @@ │ SyncMetadata( │ │ recordPrimaryKey: "1", │ │ recordType: "remindersLists", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ + │ zoneName: "external.zone", │ + │ ownerName: "external.owner", │ │ recordName: "1:remindersLists", │ │ parentRecordPrimaryKey: nil, │ │ parentRecordType: nil, │ @@ -821,27 +861,46 @@ │ id: 1, │ │ title: "Personal" │ │ ), │ - │ share: nil, │ + │ share: CKRecord( │ + │ recordID: CKRecord.ID(share-1:remindersLists/external.zone/external.owner), │ + │ recordType: "cloudkit.share", │ + │ parent: nil, │ + │ share: nil │ + │ ), │ │ _isDeleted: false, │ │ hasLastKnownServerRecord: true, │ - │ isShared: false, │ + │ isShared: true, │ │ userModificationTime: 0 │ │ ) │ ├─────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ SyncMetadata( │ │ recordPrimaryKey: "1", │ │ recordType: "reminders", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ + │ zoneName: "external.zone", │ + │ ownerName: "external.owner", │ │ recordName: "1:reminders", │ │ parentRecordPrimaryKey: "1", │ │ parentRecordType: "remindersLists", │ │ parentRecordName: "1:remindersLists", │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ + │ 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: false, │ + │ hasLastKnownServerRecord: true, │ │ isShared: false, │ │ userModificationTime: 0 │ │ ) │ @@ -865,6 +924,16 @@ share: nil ), [1]: 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" + ), + [2]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, @@ -989,10 +1058,14 @@ databaseScope: .shared, storage: [ [0]: CKRecord( - recordID: CKRecord.ID(share-1:remindersLists/external.zone/external.owner), - recordType: "cloudkit.share", - parent: nil, - share: nil + 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" ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), @@ -1058,8 +1131,8 @@ try ModelB.find(1).update { $0.modelAID = 2 }.execute(db) } -// try await syncEngine.processPendingRecordZoneChanges(scope: .private) -// try await syncEngine.processPendingRecordZoneChanges(scope: .shared) + try await syncEngine.processPendingRecordZoneChanges(scope: .private) + try await syncEngine.processPendingRecordZoneChanges(scope: .shared) assertQuery(ModelB.all, database: userDatabase.database) { """ @@ -1074,86 +1147,103 @@ } assertQuery(SyncMetadata.all, 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: "1", │ - │ recordType: "modelBs", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:modelBs", │ - │ parentRecordPrimaryKey: "2", │ - │ parentRecordType: "modelAs", │ - │ parentRecordName: "2: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: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "2:modelAs", │ - │ parentRecordPrimaryKey: nil, │ - │ parentRecordType: nil, │ - │ parentRecordName: nil, │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - └──────────────────────────────────────────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────────────────────────────────────────────────┐ + │ 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: "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: "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 │ + │ ) │ + └──────────────────────────────────────────────────────────────────────────────────────────────┘ """ } assertInlineSnapshot(of: container, as: .customDump) { @@ -1169,15 +1259,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 ) ] ), @@ -1197,6 +1278,15 @@ 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 ) ] ) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift index 39d71707..3da22697 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift @@ -273,43 +273,56 @@ try await Task.sleep(for: .seconds(1)) assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { """ - ┌─────────────────────────────────────────┐ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "remindersLists", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:remindersLists", │ - │ parentRecordPrimaryKey: nil, │ - │ parentRecordType: nil, │ - │ parentRecordName: nil, │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 0 │ - │ ) │ - ├─────────────────────────────────────────┤ - │ SyncMetadata( │ - │ recordPrimaryKey: "1", │ - │ recordType: "reminders", │ - │ zoneName: "zone", │ - │ ownerName: "__defaultOwner__", │ - │ recordName: "1:reminders", │ - │ parentRecordPrimaryKey: "1", │ - │ parentRecordType: "remindersLists", │ - │ parentRecordName: "1:remindersLists", │ - │ lastKnownServerRecord: nil, │ - │ _lastKnownServerRecordAllFields: nil, │ - │ share: nil, │ - │ _isDeleted: false, │ - │ hasLastKnownServerRecord: false, │ - │ isShared: false, │ - │ userModificationTime: 60 │ - │ ) │ - └─────────────────────────────────────────┘ + ┌───────────────────────────────────────────────────────────────────────────┐ + │ 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: nil │ + │ ), │ + │ _lastKnownServerRecordAllFields: CKRecord( │ + │ recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), │ + │ recordType: "remindersLists", │ + │ parent: nil, │ + │ share: nil, │ + │ id: 1, │ + │ isCompleted: 0, │ + │ title: "Personal" │ + │ ), │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: true, │ + │ isShared: false, │ + │ userModificationTime: 0 │ + │ ) │ + ├───────────────────────────────────────────────────────────────────────────┤ + │ SyncMetadata( │ + │ recordPrimaryKey: "1", │ + │ recordType: "reminders", │ + │ zoneName: "external.zone", │ + │ ownerName: "external.owner", │ + │ recordName: "1:reminders", │ + │ parentRecordPrimaryKey: "1", │ + │ parentRecordType: "remindersLists", │ + │ parentRecordName: "1:remindersLists", │ + │ lastKnownServerRecord: nil, │ + │ _lastKnownServerRecordAllFields: nil, │ + │ share: nil, │ + │ _isDeleted: false, │ + │ hasLastKnownServerRecord: false, │ + │ isShared: false, │ + │ userModificationTime: 60 │ + │ ) │ + └───────────────────────────────────────────────────────────────────────────┘ """ } @@ -328,6 +341,16 @@ databaseScope: .shared, storage: [ [0]: 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" + ), + [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), recordType: "remindersLists", parent: nil, @@ -383,17 +406,7 @@ ), sharedCloudDatabase: MockCloudDatabase( databaseScope: .shared, - storage: [ - [0]: CKRecord( - recordID: CKRecord.ID(1:remindersLists/external.zone/external.owner), - recordType: "remindersLists", - parent: nil, - share: nil, - id: 1, - isCompleted: 0, - title: "Personal" - ) - ] + storage: [] ) ) """ diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index cb43317c..da41dacd 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -44,24 +44,7 @@ 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"); + SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "new"."zoneName", "new"."ownerName"); END """, [2]: """ @@ -70,24 +53,7 @@ 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"); + SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "old"."zoneName", "old"."ownerName"); END """, [3]: """ @@ -456,9 +422,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -483,9 +449,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -510,7 +476,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'modelAs', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."id", 'modelAs', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -533,9 +499,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -560,9 +526,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -587,7 +553,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'parents', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."id", 'parents', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -610,7 +576,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'reminderTags', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."id", 'reminderTags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -633,9 +599,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -660,9 +626,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -687,9 +653,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -714,7 +680,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'remindersLists', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."id", 'remindersLists', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -737,7 +703,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."title", 'tags', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."title", 'tags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1012,9 +978,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -1039,9 +1005,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -1066,7 +1032,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'modelAs', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."id", 'modelAs', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1089,9 +1055,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -1116,9 +1082,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -1143,7 +1109,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'parents', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."id", 'parents', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1166,7 +1132,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'reminderTags', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."id", 'reminderTags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1189,9 +1155,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -1216,9 +1182,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -1243,9 +1209,9 @@ 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("defaultZoneName"(), (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("defaultOwnerName"(), (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") @@ -1270,7 +1236,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'remindersLists', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."id", 'remindersLists', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1293,7 +1259,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."title", 'tags', 'zone', '__defaultOwner__', NULL, NULL + SELECT "new"."title", 'tags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END diff --git a/Tests/SQLiteDataTests/Internal/Schema.swift b/Tests/SQLiteDataTests/Internal/Schema.swift index a7132784..997a49e9 100644 --- a/Tests/SQLiteDataTests/Internal/Schema.swift +++ b/Tests/SQLiteDataTests/Internal/Schema.swift @@ -82,9 +82,9 @@ func database( if attachMetadatabase { try db.attachMetadatabase(containerIdentifier: containerIdentifier) } - db.trace { - print($0.expandedDescription) - } +// db.trace { +// print($0.expandedDescription) +// } } let url = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).sqlite") let database = try DatabasePool(path: url.path(), configuration: configuration) From ab61da901b9ed1af99ca2737ddf69208654ab53c Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 18:39:11 -0500 Subject: [PATCH 03/25] wip --- .../CloudKit/Internal/Triggers.swift | 50 ++++++++++-------- .../CloudKitTests/MergeConflictTests.swift | 4 +- .../NextRecordZoneChangeBatchTests.swift | 2 +- .../SharingPermissionsTests.swift | 8 +++ .../CloudKitTests/TriggerTests.swift | 51 ++++++++++--------- 5 files changed, 68 insertions(+), 47 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index 519e2a3d..8a8b3a93 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -164,8 +164,12 @@ } onConflict: { ($0.recordPrimaryKey, $0.recordType) } doUpdate: { - $0.zoneName = $1.zoneName - $0.ownerName = $1.ownerName + $0.zoneName = Case($1.zoneName) + .when(defaultZone.zoneID.zoneName, then: $0.zoneName) + .else($1.zoneName) + $0.ownerName = Case($1.ownerName) + .when(defaultZone.zoneID.ownerName, then: $0.ownerName) + .else($1.ownerName) $0.parentRecordPrimaryKey = $1.parentRecordPrimaryKey $0.parentRecordType = $1.parentRecordType $0.userModificationTime = $1.userModificationTime @@ -199,14 +203,14 @@ ownerName: new.ownerName, oldZoneName: new.zoneName, oldOwnerName: new.ownerName -// lastKnownServerRecord: new.lastKnownServerRecord -// ?? rootServerRecord(recordName: new.recordName), -// newParentLastKnownServerRecord: parentLastKnownServerRecord( -// parentRecordPrimaryKey: new.parentRecordPrimaryKey, -// parentRecordType: new.parentRecordType -// ), -// parentRecordPrimaryKey: new.parentRecordPrimaryKey, -// parentRecordType: new.parentRecordType + // lastKnownServerRecord: new.lastKnownServerRecord + // ?? rootServerRecord(recordName: new.recordName), + // newParentLastKnownServerRecord: parentLastKnownServerRecord( + // parentRecordPrimaryKey: new.parentRecordPrimaryKey, + // parentRecordType: new.parentRecordType + // ), + // parentRecordPrimaryKey: new.parentRecordPrimaryKey, + // parentRecordType: new.parentRecordType ) ) } when: { _ in @@ -227,8 +231,8 @@ SyncMetadata .where { $0.recordName.eq(new.recordName) - && $0.recordType.eq(new.recordType) - && (new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName)) + && $0.recordType.eq(new.recordType) + && (new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName)) } .update { $0.lastKnownServerRecord = nil @@ -307,16 +311,22 @@ return ( parentRecordPrimaryKey, parentRecordType, - #sql("coalesce(\($defaultZoneName()), (\(parentMetadata.select(\.zoneName))), \(zoneName))"), - #sql("coalesce(\($defaultOwnerName()), (\(parentMetadata.select(\.ownerName))), \(ownerName))") + #sql( + "coalesce(\($defaultZoneName()), (\(parentMetadata.select(\.zoneName))), \(zoneName))" + ), + #sql( + "coalesce(\($defaultOwnerName()), (\(parentMetadata.select(\.ownerName))), \(ownerName))" + ) ) } - ?? ( - nil, - nil, - #sql("coalesce(\($defaultZoneName()), \(zoneName))"), - #sql("coalesce(\($defaultOwnerName()), \(ownerName))") - ) + ?? ( + nil, + nil, + #sql("coalesce(\($defaultZoneName()), \(zoneName))"), + #sql("coalesce(\($defaultOwnerName()), \(ownerName))") + // #sql("coalesce(\($defaultZoneName()), \(zoneName))"), + // #sql("coalesce(\($defaultOwnerName()), \(ownerName))") + ) } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) diff --git a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift index 67207373..0c9e52b3 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift @@ -629,8 +629,8 @@ 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 ) 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/SharingPermissionsTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingPermissionsTests.swift index 5ebcd451..2f867b2f 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingPermissionsTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingPermissionsTests.swift @@ -450,6 +450,14 @@ recordType: "cloudkit.share", parent: nil, share: nil + ), + [1]: 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" ) ] ) diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index da41dacd..c43877ff 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -53,6 +53,9 @@ 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"))); + UPDATE "sqlitedata_icloud_metadata" + SET "lastKnownServerRecord" = NULL, "_lastKnownServerRecordAllFields" = NULL + WHERE ((("sqlitedata_icloud_metadata"."recordName" = "new"."recordName") AND ("sqlitedata_icloud_metadata"."recordType" = "new"."recordType")) AND (("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName"))); SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "old"."zoneName", "old"."ownerName"); END """, @@ -428,7 +431,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [28]: """ @@ -455,7 +458,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [29]: """ @@ -478,7 +481,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'modelAs', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [30]: """ @@ -505,7 +508,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [31]: """ @@ -532,7 +535,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [32]: """ @@ -555,7 +558,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'parents', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [33]: """ @@ -578,7 +581,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'reminderTags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [34]: """ @@ -605,7 +608,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [35]: """ @@ -632,7 +635,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [36]: """ @@ -659,7 +662,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [37]: """ @@ -682,7 +685,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'remindersLists', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [38]: """ @@ -705,7 +708,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."title", 'tags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [39]: """ @@ -984,7 +987,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [52]: """ @@ -1011,7 +1014,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [53]: """ @@ -1034,7 +1037,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'modelAs', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [54]: """ @@ -1061,7 +1064,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [55]: """ @@ -1088,7 +1091,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [56]: """ @@ -1111,7 +1114,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'parents', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [57]: """ @@ -1134,7 +1137,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'reminderTags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [58]: """ @@ -1161,7 +1164,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [59]: """ @@ -1188,7 +1191,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [60]: """ @@ -1215,7 +1218,7 @@ 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 "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [61]: """ @@ -1238,7 +1241,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'remindersLists', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """, [62]: """ @@ -1261,7 +1264,7 @@ ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."title", 'tags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = "excluded"."zoneName", "ownerName" = "excluded"."ownerName", "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END """ ] From 298ef4058e8a6cd0df23d4b87de396360d00d554 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 19:01:48 -0500 Subject: [PATCH 04/25] wip --- Sources/SQLiteData/CloudKit/SyncEngine.swift | 6 ++- .../CloudKitTests/CloudKitTests.swift | 14 +++--- .../FetchRecordZoneChangesTests.swift | 8 ++-- .../ForeignKeyConstraintTests.swift | 24 +++++----- .../CloudKitTests/MergeConflictTests.swift | 46 +++++++++---------- 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index bba89ca1..603404b6 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -1609,8 +1609,10 @@ } onConflict: { ($0.recordPrimaryKey, $0.recordType) } doUpdate: { - // TODO: set parent fields? - $0.setLastKnownServerRecord(serverRecord) + if tablesByName[serverRecord.recordType] == nil { + // TODO: set parent fields? + $0.setLastKnownServerRecord(serverRecord) + } } .returning(\.self) .fetchOne(db) diff --git a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift index b9587199..0308ea59 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift @@ -578,12 +578,12 @@ assertQuery(RemindersList.all, database: userDatabase.database) { """ - ┌─────────────────────┐ - │ RemindersList( │ - │ id: 1, │ - │ title: "Personal" │ - │ ) │ - └─────────────────────┘ + ┌─────────────────┐ + │ RemindersList( │ + │ id: 1, │ + │ title: "Work" │ + │ ) │ + └─────────────────┘ """ } assertQuery( @@ -653,7 +653,7 @@ parent: nil, share: nil, id: 1, - title: "My stuff" + title: "Personal" ) ] ), diff --git a/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift b/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift index 7c4de2f0..68beaac4 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift @@ -152,7 +152,7 @@ │ isCompleted: true, │ │ priority: nil, │ │ title: "Get milk", │ - │ remindersListID: 1 │ + │ remindersListID: 2 │ │ ) │ └──────────────────────┘ """ @@ -163,7 +163,7 @@ ) { """ ┌────────────────────┬────────────────────┐ - │ "1:reminders" │ "1:remindersLists" │ + │ "1:reminders" │ "2:remindersLists" │ │ "1:remindersLists" │ nil │ │ "2:remindersLists" │ nil │ └────────────────────┴────────────────────┘ @@ -178,11 +178,11 @@ [0]: CKRecord( recordID: CKRecord.ID(1:reminders/zone/__defaultOwner__), recordType: "reminders", - parent: CKReference(recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__)), + parent: CKReference(recordID: CKRecord.ID(2:remindersLists/zone/__defaultOwner__)), share: nil, id: 1, isCompleted: 1, - remindersListID: 1, + remindersListID: 2, title: "Get milk" ), [1]: CKRecord( diff --git a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift index 04973165..24210644 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift @@ -509,7 +509,7 @@ ) { """ ┌────────────────────┬────────────────────┐ - │ "1:reminders" │ "1:remindersLists" │ + │ "1:reminders" │ "2:remindersLists" │ │ "1:remindersLists" │ nil │ │ "2:remindersLists" │ nil │ └────────────────────┴────────────────────┘ @@ -524,7 +524,7 @@ │ isCompleted: false, │ │ priority: nil, │ │ title: "Get milk", │ - │ remindersListID: 1 │ + │ remindersListID: 2 │ │ ) │ └───────────────────────┘ """ @@ -643,7 +643,7 @@ │ "1:remindersLists" │ nil │ │ "2:remindersLists" │ nil │ │ "3:remindersLists" │ nil │ - │ "1:reminders" │ "3:remindersLists" │ + │ "1:reminders" │ "2:remindersLists" │ └────────────────────┴────────────────────┘ """ } @@ -655,8 +655,8 @@ │ dueDate: nil, │ │ isCompleted: false, │ │ priority: nil, │ - │ title: "Buy milk", │ - │ remindersListID: 3 │ + │ title: "Get milk", │ + │ remindersListID: 2 │ │ ) │ └───────────────────────┘ """ @@ -671,12 +671,12 @@ CKRecord( recordID: CKRecord.ID(1:reminders/zone/__defaultOwner__), recordType: "reminders", - parent: CKReference(recordID: CKRecord.ID(3:remindersLists/zone/__defaultOwner__)), + parent: CKReference(recordID: CKRecord.ID(2:remindersLists/zone/__defaultOwner__)), share: nil, id: 1, isCompleted: 0, - remindersListID: 3, - title: "Buy milk" + remindersListID: 2, + title: "Get milk" ) """ } @@ -738,7 +738,7 @@ │ "1:remindersLists" │ nil │ │ "2:remindersLists" │ nil │ │ "3:remindersLists" │ nil │ - │ "1:reminders" │ "3:remindersLists" │ + │ "1:reminders" │ "2:remindersLists" │ └────────────────────┴────────────────────┘ """ } @@ -751,7 +751,7 @@ │ isCompleted: false, │ │ priority: nil, │ │ title: "Get milk", │ - │ remindersListID: 3 │ + │ remindersListID: 2 │ │ ) │ └───────────────────────┘ """ @@ -766,11 +766,11 @@ CKRecord( recordID: CKRecord.ID(1:reminders/zone/__defaultOwner__), recordType: "reminders", - parent: CKReference(recordID: CKRecord.ID(3:remindersLists/zone/__defaultOwner__)), + parent: CKReference(recordID: CKRecord.ID(2:remindersLists/zone/__defaultOwner__)), share: nil, id: 1, isCompleted: 0, - remindersListID: 3, + remindersListID: 2, title: "Get milk" ) """ diff --git a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift index 0c9e52b3..9867710e 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift @@ -137,11 +137,11 @@ share: nil, id: 1, id🗓️: 0, - isCompleted: 1, - isCompleted🗓️: 60, + isCompleted: 0, + isCompleted🗓️: 0, remindersListID: 1, remindersListID🗓️: 0, - title: "", + title: "Buy milk", title🗓️: 60, 🗓️: 60 ), @@ -291,11 +291,11 @@ share: nil, id: 1, id🗓️: 0, - isCompleted: 1, - isCompleted🗓️: 30, + isCompleted: 0, + isCompleted🗓️: 0, remindersListID: 1, remindersListID🗓️: 0, - title: "", + title: "Buy milk", title🗓️: 30, 🗓️: 30 ), @@ -360,11 +360,11 @@ share: nil, id: 1, id🗓️: 0, - isCompleted: 1, - isCompleted🗓️: 30, + isCompleted: 0, + isCompleted🗓️: 0, remindersListID: 1, remindersListID🗓️: 0, - title: "", + title: "Buy milk", title🗓️: 30, 🗓️: 30 ), @@ -428,7 +428,7 @@ │ dueDate: nil, │ │ isCompleted: false, │ │ priority: nil, │ - │ title: "Get milk", │ + │ title: "Buy milk", │ │ remindersListID: 1 │ │ ) │ └───────────────────────┘ @@ -451,7 +451,7 @@ isCompleted🗓️: 0, remindersListID: 1, remindersListID🗓️: 0, - title: "Get milk", + title: "Buy milk", title🗓️: 60, 🗓️: 60 ), @@ -520,7 +520,7 @@ isCompleted🗓️: 0, remindersListID: 1, remindersListID🗓️: 0, - title: "Get milk", + title: "Buy milk", title🗓️: 30, 🗓️: 30 ), @@ -590,7 +590,7 @@ isCompleted🗓️: 0, remindersListID: 1, remindersListID🗓️: 0, - title: "Get milk", + title: "Buy milk", title🗓️: 30, 🗓️: 30 ), @@ -632,24 +632,23 @@ 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( @@ -661,18 +660,17 @@ recordType: "reminders", parent: CKReference(recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__)), share: nil, - dueDate🗓️: 1, + dueDate: Date(1970-01-01T00:00:30.000Z), + dueDate🗓️: 0, id: 1, id🗓️: 0, isCompleted: 0, isCompleted🗓️: 0, - priority: 3, - priority🗓️: 1, remindersListID: 1, remindersListID🗓️: 0, title: "", title🗓️: 0, - 🗓️: 1 + 🗓️: 0 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), From 4fa2393fddd48e7751a07443e34aefdbf631c767 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 19:09:50 -0500 Subject: [PATCH 05/25] wip --- Sources/SQLiteData/CloudKit/SyncEngine.swift | 4 ++- .../CloudKitTests/CloudKitTests.swift | 2 +- .../FetchRecordZoneChangesTests.swift | 2 +- .../ForeignKeyConstraintTests.swift | 20 +++++------ .../CloudKitTests/MergeConflictTests.swift | 36 ++++++++++--------- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 603404b6..08d09e5d 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -1608,10 +1608,12 @@ ) } onConflict: { ($0.recordPrimaryKey, $0.recordType) - } doUpdate: { + } doUpdate: { if tablesByName[serverRecord.recordType] == nil { // TODO: set parent fields? $0.setLastKnownServerRecord(serverRecord) + } else { + $0.recordType = $0.recordType } } .returning(\.self) diff --git a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift index 0308ea59..57eb9b3c 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift @@ -653,7 +653,7 @@ parent: nil, share: nil, id: 1, - title: "Personal" + title: "My stuff" ) ] ), diff --git a/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift b/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift index 68beaac4..6e6007af 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift @@ -629,7 +629,7 @@ │ _isDeleted: false, │ │ hasLastKnownServerRecord: true, │ │ isShared: false, │ - │ userModificationTime: -1 │ + │ userModificationTime: 0 │ │ ) │ └────────────────────────────────────────────────────────────┘ """ diff --git a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift index 24210644..40688a42 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift @@ -643,7 +643,7 @@ │ "1:remindersLists" │ nil │ │ "2:remindersLists" │ nil │ │ "3:remindersLists" │ nil │ - │ "1:reminders" │ "2:remindersLists" │ + │ "1:reminders" │ "3:remindersLists" │ └────────────────────┴────────────────────┘ """ } @@ -655,8 +655,8 @@ │ dueDate: nil, │ │ isCompleted: false, │ │ priority: nil, │ - │ title: "Get milk", │ - │ remindersListID: 2 │ + │ title: "Buy milk", │ + │ remindersListID: 3 │ │ ) │ └───────────────────────┘ """ @@ -671,12 +671,12 @@ CKRecord( recordID: CKRecord.ID(1:reminders/zone/__defaultOwner__), recordType: "reminders", - parent: CKReference(recordID: CKRecord.ID(2:remindersLists/zone/__defaultOwner__)), + parent: CKReference(recordID: CKRecord.ID(3:remindersLists/zone/__defaultOwner__)), share: nil, id: 1, isCompleted: 0, - remindersListID: 2, - title: "Get milk" + remindersListID: 3, + title: "Buy milk" ) """ } @@ -738,7 +738,7 @@ │ "1:remindersLists" │ nil │ │ "2:remindersLists" │ nil │ │ "3:remindersLists" │ nil │ - │ "1:reminders" │ "2:remindersLists" │ + │ "1:reminders" │ "3:remindersLists" │ └────────────────────┴────────────────────┘ """ } @@ -751,7 +751,7 @@ │ isCompleted: false, │ │ priority: nil, │ │ title: "Get milk", │ - │ remindersListID: 2 │ + │ remindersListID: 3 │ │ ) │ └───────────────────────┘ """ @@ -766,11 +766,11 @@ CKRecord( recordID: CKRecord.ID(1:reminders/zone/__defaultOwner__), recordType: "reminders", - parent: CKReference(recordID: CKRecord.ID(2:remindersLists/zone/__defaultOwner__)), + parent: CKReference(recordID: CKRecord.ID(3:remindersLists/zone/__defaultOwner__)), share: nil, id: 1, isCompleted: 0, - remindersListID: 2, + remindersListID: 3, title: "Get milk" ) """ diff --git a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift index 9867710e..e3a5964a 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift @@ -137,8 +137,8 @@ share: nil, id: 1, id🗓️: 0, - isCompleted: 0, - isCompleted🗓️: 0, + isCompleted: 1, + isCompleted🗓️: 30, remindersListID: 1, remindersListID🗓️: 0, title: "Buy milk", @@ -291,13 +291,13 @@ share: nil, id: 1, id🗓️: 0, - isCompleted: 0, - isCompleted🗓️: 0, + isCompleted: 1, + isCompleted🗓️: 60, remindersListID: 1, remindersListID🗓️: 0, title: "Buy milk", title🗓️: 30, - 🗓️: 30 + 🗓️: 60 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), @@ -360,13 +360,13 @@ share: nil, id: 1, id🗓️: 0, - isCompleted: 0, - isCompleted🗓️: 0, + isCompleted: 1, + isCompleted🗓️: 60, remindersListID: 1, remindersListID🗓️: 0, title: "Buy milk", title🗓️: 30, - 🗓️: 30 + 🗓️: 60 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), @@ -428,7 +428,7 @@ │ dueDate: nil, │ │ isCompleted: false, │ │ priority: nil, │ - │ title: "Buy milk", │ + │ title: "Get milk", │ │ remindersListID: 1 │ │ ) │ └───────────────────────┘ @@ -451,7 +451,7 @@ isCompleted🗓️: 0, remindersListID: 1, remindersListID🗓️: 0, - title: "Buy milk", + title: "Get milk", title🗓️: 60, 🗓️: 60 ), @@ -520,9 +520,9 @@ isCompleted🗓️: 0, remindersListID: 1, remindersListID🗓️: 0, - title: "Buy milk", - title🗓️: 30, - 🗓️: 30 + title: "Get milk", + title🗓️: 60, + 🗓️: 60 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), @@ -590,9 +590,9 @@ isCompleted🗓️: 0, remindersListID: 1, remindersListID🗓️: 0, - title: "Buy milk", - title🗓️: 30, - 🗓️: 30 + title: "Get milk", + title🗓️: 60, + 🗓️: 60 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), @@ -666,11 +666,13 @@ id🗓️: 0, isCompleted: 0, isCompleted🗓️: 0, + priority: 3, + priority🗓️: 1, remindersListID: 1, remindersListID🗓️: 0, title: "", title🗓️: 0, - 🗓️: 0 + 🗓️: 1 ), [1]: CKRecord( recordID: CKRecord.ID(1:remindersLists/zone/__defaultOwner__), From eaad4acd0dfba1963210bd50024c4e70c51d2817 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 19:18:55 -0500 Subject: [PATCH 06/25] wip --- .../CloudKitTests/SharingTests.swift | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index d412adb3..ef0e7da5 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -1035,17 +1035,9 @@ try await syncEngine.processPendingRecordZoneChanges(scope: .shared) - assertQuery( - SyncMetadata.select { ($0.recordName, $0.share) }, - database: syncEngine.metadatabase - ) { - """ - ┌────────────────────┬─────┐ - │ "1:remindersLists" │ nil │ - │ "1:reminders" │ nil │ - └────────────────────┴─────┘ - """ - } + assertQuery(Reminder.all, database: userDatabase.database) + assertQuery(RemindersList.all, database: userDatabase.database) + assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) assertInlineSnapshot(of: container, as: .customDump) { """ From 250a1a063e72ab907e95385103e13dfe2fcfa927 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 21:00:56 -0500 Subject: [PATCH 07/25] wip --- .../CloudKit/Internal/Triggers.swift | 106 +++++++++++++--- Sources/SQLiteData/CloudKit/SyncEngine.swift | 74 ++++++----- .../SQLiteData/CloudKit/SyncMetadata.swift | 29 ----- .../CloudKitTests/SharingTests.swift | 119 +++++++++++++----- .../CloudKitTests/TriggerTests.swift | 35 +++++- 5 files changed, 252 insertions(+), 111 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index 8a8b3a93..f2936e3b 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -202,15 +202,8 @@ zoneName: new.zoneName, ownerName: new.ownerName, oldZoneName: new.zoneName, - oldOwnerName: new.ownerName - // lastKnownServerRecord: new.lastKnownServerRecord - // ?? rootServerRecord(recordName: new.recordName), - // newParentLastKnownServerRecord: parentLastKnownServerRecord( - // parentRecordPrimaryKey: new.parentRecordPrimaryKey, - // parentRecordType: new.parentRecordType - // ), - // parentRecordPrimaryKey: new.parentRecordPrimaryKey, - // parentRecordType: new.parentRecordType + oldOwnerName: new.ownerName, + childRecordNames: #bind(nil) ) ) } when: { _ in @@ -224,17 +217,22 @@ createTemporaryTrigger( "after_update_on_sqlitedata_icloud_metadata", ifNotExists: true, - after: .update { - old, - new in + after: .update { old, new in + let zoneChanged = new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName) + validate(recordName: new.recordName) SyncMetadata .where { - $0.recordName.eq(new.recordName) - && $0.recordType.eq(new.recordType) - && (new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName)) + zoneChanged + && $0.recordName.in( + childrenRecordNames(recordName: new.recordName, includeRecord: true) { + $0.select(\.recordName) + } + ) } .update { + $0.zoneName = new.zoneName + $0.ownerName = new.ownerName $0.lastKnownServerRecord = nil $0._lastKnownServerRecordAllFields = nil } @@ -244,7 +242,16 @@ zoneName: new.zoneName, ownerName: new.ownerName, oldZoneName: old.zoneName, - oldOwnerName: old.ownerName + oldOwnerName: old.ownerName, + childRecordNames: #sql( + """ + iif( + \(zoneChanged), + \(childrenRecordNames(recordName: new.recordName, includeRecord: false) { $0.select { $0.recordName.jsonGroupArray() } }), + NULL + ) + """ + ) ) ) } when: { old, new in @@ -385,6 +392,38 @@ } } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + private func childrenRecordNames( + recordName: some QueryExpression, + includeRecord: Bool, + select: (Where) -> Select + ) -> some Statement { + With { + SyncMetadata + .where { $0.recordName.eq(recordName) } + .select { ChildMetadata.Columns(recordName: $0.recordName, parentRecordName: #bind(nil)) } + .union( + all: true, + SyncMetadata + .select { + ChildMetadata.Columns( + recordName: $0.recordName, + parentRecordName: $0.parentRecordName + ) + } + .join(ChildMetadata.all) { $0.parentRecordName.eq($1.recordName) } + ) + } query: { + select( + ChildMetadata.where { + if !includeRecord { + $0.recordName.neq(recordName) + } + } + ) + } + } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) private func rootServerRecord( recordName: some QueryExpression @@ -435,4 +474,39 @@ substr(1, 1).neq("_") && octetLength().lte(255) && octetLength().eq(length()) } } + + @Table @Selection + struct ChildMetadata { + let recordName: String + let parentRecordName: String? + } + + @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? + } #endif diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 08d09e5d..457165aa 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -609,11 +609,8 @@ String, String, String, - String -// CKRecord?.SystemFieldsRepresentation, -// CKRecord?.SystemFieldsRepresentation, -// String?, -// String? + String, + [String]?.JSONRepresentation ) -> Void).self ) func didUpdate( @@ -621,16 +618,13 @@ zoneName: String, ownerName: String, oldZoneName: String, - oldOwnerName: String -// lastKnownServerRecord: CKRecord?, -// newParentLastKnownServerRecord: CKRecord?, -// parentRecordPrimaryKey: String? = nil, -// parentRecordType: String? = nil + oldOwnerName: String, + childRecordNames: [String]? ) throws { let zoneID = CKRecordZone.ID.init( zoneName: oldZoneName, ownerName: oldOwnerName - ) // lastKnownServerRecord?.recordID.zoneID ?? defaultZone.zoneID + ) // lastKnownServerRecord?.recordID.zoneID ?? defaultZone.zoneID let newZoneID = CKRecordZone.ID.init(zoneName: zoneName, ownerName: ownerName) //newParentLastKnownServerRecord?.recordID.zoneID if zoneID != newZoneID { @@ -654,8 +648,14 @@ } syncEngine?.state .add(pendingRecordZoneChanges: [ - .deleteRecord(CKRecord.ID.init(recordName: recordName, zoneID: zoneID)) + .deleteRecord(CKRecord.ID(recordName: recordName, zoneID: zoneID)) ]) + for childRecordName in childRecordNames ?? [] { + syncEngine?.state + .add(pendingRecordZoneChanges: [ + .deleteRecord(CKRecord.ID(recordName: childRecordName, zoneID: zoneID)) + ]) + } let newSyncEngine = self.syncEngines.withValue { newZoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared @@ -664,6 +664,12 @@ .add(pendingRecordZoneChanges: [ .saveRecord(CKRecord.ID.init(recordName: recordName, zoneID: newZoneID)) ]) + for childRecordName in childRecordNames ?? [] { + newSyncEngine?.state + .add(pendingRecordZoneChanges: [ + .saveRecord(CKRecord.ID(recordName: childRecordName, zoneID: newZoneID)) + ]) + } return } @@ -920,6 +926,7 @@ } #endif + print("!!!") let batch = await syncEngine.recordZoneChangeBatch(pendingChanges: changes) { recordID in var missingTable: CKRecord.ID? var missingRecord: CKRecord.ID? @@ -1608,7 +1615,7 @@ ) } onConflict: { ($0.recordPrimaryKey, $0.recordType) - } doUpdate: { + } doUpdate: { if tablesByName[serverRecord.recordType] == nil { // TODO: set parent fields? $0.setLastKnownServerRecord(serverRecord) @@ -1616,7 +1623,7 @@ $0.recordType = $0.recordType } } - .returning(\.self) + .returning(\.self) .fetchOne(db) guard let table = tablesByName[serverRecord.recordType] @@ -1624,10 +1631,10 @@ return } -// let metadata = -// try SyncMetadata -// .find(serverRecord.recordID) -// .fetchOne(db) + // let metadata = + // try SyncMetadata + // .find(serverRecord.recordID) + // .fetchOne(db) serverRecord.userModificationTime = metadata?.userModificationTime ?? serverRecord.userModificationTime @@ -1646,11 +1653,11 @@ : nil ) } else { -// reportIssue( -// """ -// Local database record could not be found for '\(serverRecord.recordID.recordName)'. -// """ -// ) + // reportIssue( + // """ + // Local database record could not be found for '\(serverRecord.recordID.recordName)'. + // """ + // ) } columnNames = _columnNames } else { @@ -2151,13 +2158,16 @@ } @TaskLocal package var _isSynchronizingChanges = false -@TaskLocal package var _defaultZoneID: CKRecordZone.ID? -@DatabaseFunction -func defaultZoneName() -> String? { - _defaultZoneID?.zoneName -} -@DatabaseFunction -func defaultOwnerName() -> String? { - _defaultZoneID?.ownerName -} + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @TaskLocal package var _defaultZoneID: CKRecordZone.ID? + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @DatabaseFunction + func defaultZoneName() -> String? { + _defaultZoneID?.zoneName + } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @DatabaseFunction + func defaultOwnerName() -> String? { + _defaultZoneID?.ownerName + } #endif diff --git a/Sources/SQLiteData/CloudKit/SyncMetadata.swift b/Sources/SQLiteData/CloudKit/SyncMetadata.swift index 0f655fd2..78a99930 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( diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index ef0e7da5..b757ba75 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -1080,6 +1080,7 @@ 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) @@ -1119,13 +1120,17 @@ ) ) - try await self.userDatabase.userWrite { db in - try ModelB.find(1).update { $0.modelAID = 2 }.execute(db) + 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) } - try await syncEngine.processPendingRecordZoneChanges(scope: .private) - try await syncEngine.processPendingRecordZoneChanges(scope: .shared) - assertQuery(ModelB.all, database: userDatabase.database) { """ ┌───────────────┐ @@ -1137,7 +1142,21 @@ └───────────────┘ """ } - 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( │ @@ -1171,6 +1190,41 @@ │ ) │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ 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", │ @@ -1198,41 +1252,37 @@ │ _isDeleted: false, │ │ hasLastKnownServerRecord: true, │ │ isShared: false, │ - │ userModificationTime: 0 │ + │ userModificationTime: 1 │ │ ) │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ SyncMetadata( │ - │ recordPrimaryKey: "2", │ - │ recordType: "modelAs", │ + │ recordPrimaryKey: "1", │ + │ recordType: "modelCs", │ │ zoneName: "external.zone", │ │ ownerName: "external.owner", │ - │ recordName: "2:modelAs", │ - │ parentRecordPrimaryKey: nil, │ - │ parentRecordType: nil, │ - │ parentRecordName: nil, │ + │ recordName: "1:modelCs", │ + │ parentRecordPrimaryKey: "1", │ + │ parentRecordType: "modelBs", │ + │ parentRecordName: "1:modelBs", │ │ 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)) │ + │ 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(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 │ + │ 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: true, │ + │ isShared: false, │ │ userModificationTime: 0 │ │ ) │ └──────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -1279,6 +1329,15 @@ 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 c43877ff..7c6cea8d 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -44,7 +44,7 @@ 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"); + SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "new"."zoneName", "new"."ownerName", NULL); END """, [2]: """ @@ -54,9 +54,36 @@ 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"))); UPDATE "sqlitedata_icloud_metadata" - SET "lastKnownServerRecord" = NULL, "_lastKnownServerRecordAllFields" = NULL - WHERE ((("sqlitedata_icloud_metadata"."recordName" = "new"."recordName") AND ("sqlitedata_icloud_metadata"."recordType" = "new"."recordType")) AND (("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName"))); - SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "old"."zoneName", "old"."ownerName"); + SET "zoneName" = "new"."zoneName", "ownerName" = "new"."ownerName", "lastKnownServerRecord" = NULL, "_lastKnownServerRecordAllFields" = NULL + WHERE ((("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")) AND ("sqlitedata_icloud_metadata"."recordName" IN (WITH "childMetadatas" 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 "childMetadatas" ON ("sqlitedata_icloud_metadata"."parentRecordName" = "childMetadatas"."recordName") + ) + SELECT "childMetadatas"."recordName" + FROM "childMetadatas"))); + SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "old"."zoneName", "old"."ownerName", iif( + (("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")), + ( + WITH "childMetadatas" 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 "childMetadatas" ON ("sqlitedata_icloud_metadata"."parentRecordName" = "childMetadatas"."recordName") + ) + SELECT json_group_array("childMetadatas"."recordName") + FROM "childMetadatas" + WHERE ("childMetadatas"."recordName" <> "new"."recordName") + ), + NULL + )); END """, [3]: """ From 92cd1036ec11c66a136d2a9db3e2cae09048ca27 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 21:30:33 -0500 Subject: [PATCH 08/25] wip --- .../CloudKit/Internal/Triggers.swift | 1 - Sources/SQLiteData/CloudKit/SyncEngine.swift | 136 +++++++----------- Tests/SQLiteDataTests/AssertQueryTests.swift | 2 +- .../Internal/BaseCloudKitTests.swift | 2 +- 4 files changed, 52 insertions(+), 89 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index f2936e3b..2d88cb56 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -219,7 +219,6 @@ ifNotExists: true, after: .update { old, new in let zoneChanged = new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName) - validate(recordName: new.recordName) SyncMetadata .where { diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 457165aa..9e3dfba8 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -620,82 +620,59 @@ oldZoneName: String, oldOwnerName: String, childRecordNames: [String]? - ) throws { - let zoneID = CKRecordZone.ID.init( - zoneName: oldZoneName, - ownerName: oldOwnerName - ) // lastKnownServerRecord?.recordID.zoneID ?? defaultZone.zoneID - let newZoneID = CKRecordZone.ID.init(zoneName: zoneName, ownerName: ownerName) - //newParentLastKnownServerRecord?.recordID.zoneID - if 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. - // """ - // } - // } - // throw ZoneChangingError(recordName: recordName, zoneID: zoneID, newZoneID: newZoneID) - let syncEngine = self.syncEngines.withValue { - zoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared - } - syncEngine?.state - .add(pendingRecordZoneChanges: [ - .deleteRecord(CKRecord.ID(recordName: recordName, zoneID: zoneID)) - ]) - for childRecordName in childRecordNames ?? [] { - syncEngine?.state - .add(pendingRecordZoneChanges: [ - .deleteRecord(CKRecord.ID(recordName: childRecordName, zoneID: zoneID)) - ]) - } + ) { + var oldChanges: [CKSyncEngine.PendingRecordZoneChange] = [] + var newChanges: [CKSyncEngine.PendingRecordZoneChange] = [] + + let oldZoneID = CKRecordZone.ID(zoneName: oldZoneName, ownerName: oldOwnerName) + let zoneID = CKRecordZone.ID.init(zoneName: zoneName, ownerName: ownerName) - let newSyncEngine = self.syncEngines.withValue { - newZoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared + if oldZoneID != zoneID { + oldChanges.append(.deleteRecord(CKRecord.ID(recordName: recordName, zoneID: oldZoneID))) + for childRecordName in childRecordNames ?? [] { + oldChanges.append( + .deleteRecord(CKRecord.ID(recordName: childRecordName, zoneID: oldZoneID)) + ) } - newSyncEngine?.state - .add(pendingRecordZoneChanges: [ - .saveRecord(CKRecord.ID.init(recordName: recordName, zoneID: newZoneID)) - ]) + newChanges.append(.saveRecord(CKRecord.ID(recordName: recordName, zoneID: zoneID))) for childRecordName in childRecordNames ?? [] { - newSyncEngine?.state - .add(pendingRecordZoneChanges: [ - .saveRecord(CKRecord.ID(recordName: childRecordName, zoneID: newZoneID)) - ]) + newChanges.append(.saveRecord(CKRecord.ID(recordName: childRecordName, zoneID: zoneID))) } - return - } - - let change = CKSyncEngine.PendingRecordZoneChange.saveRecord( - CKRecord.ID( - recordName: recordName, - zoneID: zoneID + } else { + newChanges.append( + .saveRecord( + CKRecord.ID( + recordName: recordName, + zoneID: zoneID + ) + ) ) - ) + } guard isRunning else { - Task { + // TODO: can this be done in the trigger?? + 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( @@ -1617,10 +1594,10 @@ ($0.recordPrimaryKey, $0.recordType) } doUpdate: { if tablesByName[serverRecord.recordType] == nil { - // TODO: set parent fields? $0.setLastKnownServerRecord(serverRecord) } else { - $0.recordType = $0.recordType + // NB: Keep this to allow for "RETURNING *" to work below: + $0.recordPrimaryKey = $0.recordPrimaryKey } } .returning(\.self) @@ -1631,37 +1608,24 @@ return } - // let metadata = - // try SyncMetadata - // .find(serverRecord.recordID) - // .fetchOne(db) serverRecord.userModificationTime = metadata?.userModificationTime ?? serverRecord.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 metadata, + 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 { 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/Internal/BaseCloudKitTests.swift b/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift index ba37e57b..5e34de4e 100644 --- a/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift +++ b/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift @@ -7,7 +7,7 @@ import Testing import os @Suite( - .snapshots(record: .failed), + .snapshots(record: .missing), .dependencies { $0.currentTime.now = 0 $0.dataManager = InMemoryDataManager() From c9d3ce36bc72543ca92dad1e0163fcaf4808a0c3 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 21:31:21 -0500 Subject: [PATCH 09/25] wip --- Sources/SQLiteData/CloudKit/Internal/Triggers.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index 2d88cb56..ebabbb21 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -330,8 +330,6 @@ nil, #sql("coalesce(\($defaultZoneName()), \(zoneName))"), #sql("coalesce(\($defaultOwnerName()), \(ownerName))") - // #sql("coalesce(\($defaultZoneName()), \(zoneName))"), - // #sql("coalesce(\($defaultOwnerName()), \(ownerName))") ) } From fa59325cc525c7e7c8bd2c4642915ab4ebce5a5c Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 21:36:05 -0500 Subject: [PATCH 10/25] wip --- .../CloudKitTests/SharingTests.swift | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index b757ba75..805d3687 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -1345,6 +1345,282 @@ """ } } + + @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" + ) + ] + ) + ) + """ + } + } } } #endif From 40e269794b83e6a13f558468435c6158791b2d3e Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 15 Sep 2025 21:42:32 -0500 Subject: [PATCH 11/25] wip --- Sources/SQLiteData/CloudKit/SyncEngine.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 9e3dfba8..da042b64 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -573,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) @@ -903,7 +898,6 @@ } #endif - print("!!!") let batch = await syncEngine.recordZoneChangeBatch(pendingChanges: changes) { recordID in var missingTable: CKRecord.ID? var missingRecord: CKRecord.ID? From 58c90d20289c8665e804c013b142ed7d2ff9b35e Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 09:51:43 -0500 Subject: [PATCH 12/25] clean up --- .../CloudKit/Internal/Triggers.swift | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index ebabbb21..a7b0edaf 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -219,15 +219,23 @@ ifNotExists: true, after: .update { old, new in let zoneChanged = new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName) + let selfAndDescendantRecordNames = descendantRecordNames( + recordName: new.recordName, + includeRecord: true + ) { + $0.select(\.recordName) + } + let descendantRecordNamesJSON = descendantRecordNames( + recordName: new.recordName, + includeRecord: false + ) { + $0.select { $0.recordName.jsonGroupArray() } + } + validate(recordName: new.recordName) SyncMetadata .where { - zoneChanged - && $0.recordName.in( - childrenRecordNames(recordName: new.recordName, includeRecord: true) { - $0.select(\.recordName) - } - ) + zoneChanged && $0.recordName.in(selfAndDescendantRecordNames) } .update { $0.zoneName = new.zoneName @@ -246,7 +254,7 @@ """ iif( \(zoneChanged), - \(childrenRecordNames(recordName: new.recordName, includeRecord: false) { $0.select { $0.recordName.jsonGroupArray() } }), + \(descendantRecordNamesJSON), NULL ) """ @@ -390,29 +398,29 @@ } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - private func childrenRecordNames( + private func descendantRecordNames( recordName: some QueryExpression, includeRecord: Bool, - select: (Where) -> Select + select: (Where) -> Select ) -> some Statement { With { SyncMetadata .where { $0.recordName.eq(recordName) } - .select { ChildMetadata.Columns(recordName: $0.recordName, parentRecordName: #bind(nil)) } + .select { DescendantMetadata.Columns(recordName: $0.recordName, parentRecordName: #bind(nil)) } .union( all: true, SyncMetadata .select { - ChildMetadata.Columns( + DescendantMetadata.Columns( recordName: $0.recordName, parentRecordName: $0.parentRecordName ) } - .join(ChildMetadata.all) { $0.parentRecordName.eq($1.recordName) } + .join(DescendantMetadata.all) { $0.parentRecordName.eq($1.recordName) } ) } query: { select( - ChildMetadata.where { + DescendantMetadata.where { if !includeRecord { $0.recordName.neq(recordName) } @@ -473,7 +481,7 @@ } @Table @Selection - struct ChildMetadata { + struct DescendantMetadata { let recordName: String let parentRecordName: String? } From bb7713b0ecdde76a3dc1d06ff08ccd9f7be4ee45 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 10:05:03 -0500 Subject: [PATCH 13/25] fix snapshot --- .../CloudKitTests/TriggerTests.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index 7c6cea8d..ba29ae03 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -55,32 +55,32 @@ WHERE NOT (((substr("new"."recordName", 1, 1) <> '_') AND (octet_length("new"."recordName") <= 255)) AND (octet_length("new"."recordName") = length("new"."recordName"))); UPDATE "sqlitedata_icloud_metadata" SET "zoneName" = "new"."zoneName", "ownerName" = "new"."ownerName", "lastKnownServerRecord" = NULL, "_lastKnownServerRecordAllFields" = NULL - WHERE ((("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")) AND ("sqlitedata_icloud_metadata"."recordName" IN (WITH "childMetadatas" AS ( + WHERE ((("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")) AND ("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 "childMetadatas" ON ("sqlitedata_icloud_metadata"."parentRecordName" = "childMetadatas"."recordName") + JOIN "descendantMetadatas" ON ("sqlitedata_icloud_metadata"."parentRecordName" = "descendantMetadatas"."recordName") ) - SELECT "childMetadatas"."recordName" - FROM "childMetadatas"))); + SELECT "descendantMetadatas"."recordName" + FROM "descendantMetadatas"))); SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "old"."zoneName", "old"."ownerName", iif( (("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")), ( - WITH "childMetadatas" AS ( + 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 "childMetadatas" ON ("sqlitedata_icloud_metadata"."parentRecordName" = "childMetadatas"."recordName") + JOIN "descendantMetadatas" ON ("sqlitedata_icloud_metadata"."parentRecordName" = "descendantMetadatas"."recordName") ) - SELECT json_group_array("childMetadatas"."recordName") - FROM "childMetadatas" - WHERE ("childMetadatas"."recordName" <> "new"."recordName") + SELECT json_group_array("descendantMetadatas"."recordName") + FROM "descendantMetadatas" + WHERE ("descendantMetadatas"."recordName" <> "new"."recordName") ), NULL )); From 535a4fd2db12f2f84e45e7016654d67be46302a4 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 12:21:45 -0500 Subject: [PATCH 14/25] wip --- .../CloudKit/Internal/Triggers.swift | 42 ++++------ Sources/SQLiteData/CloudKit/SyncEngine.swift | 66 ++++++++------- .../CloudKitTests/CloudKitTests.swift | 12 +-- .../CloudKitTests/SharingTests.swift | 68 +++++++++++++++ .../CloudKitTests/TriggerTests.swift | 84 +++++++++---------- 5 files changed, 169 insertions(+), 103 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index a7b0edaf..5c9f7658 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -203,7 +203,7 @@ ownerName: new.ownerName, oldZoneName: new.zoneName, oldOwnerName: new.ownerName, - childRecordNames: #bind(nil) + descendantRecordNames: #bind(nil) ) ) } when: { _ in @@ -221,13 +221,13 @@ let zoneChanged = new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName) let selfAndDescendantRecordNames = descendantRecordNames( recordName: new.recordName, - includeRecord: true + includeSelf: true ) { $0.select(\.recordName) } let descendantRecordNamesJSON = descendantRecordNames( recordName: new.recordName, - includeRecord: false + includeSelf: false ) { $0.select { $0.recordName.jsonGroupArray() } } @@ -250,15 +250,7 @@ ownerName: new.ownerName, oldZoneName: old.zoneName, oldOwnerName: old.ownerName, - childRecordNames: #sql( - """ - iif( - \(zoneChanged), - \(descendantRecordNamesJSON), - NULL - ) - """ - ) + descendantRecordNames: Case().when(zoneChanged, then: descendantRecordNamesJSON) ) ) } when: { old, new in @@ -300,11 +292,11 @@ zoneName: SQLQueryExpression, ownerName: SQLQueryExpression ) { - let zoneName = #sql( + let defaultZoneName = #sql( "\(quote: defaultZone.zoneID.zoneName, delimiter: .text)", as: String.self ) - let ownerName = #sql( + let defaultOwnerName = #sql( "\(quote: defaultZone.zoneID.ownerName, delimiter: .text)", as: String.self ) @@ -326,18 +318,18 @@ parentRecordPrimaryKey, parentRecordType, #sql( - "coalesce(\($defaultZoneName()), (\(parentMetadata.select(\.zoneName))), \(zoneName))" + "coalesce(\($currentZoneName()), (\(parentMetadata.select(\.zoneName))), \(defaultZoneName))" ), #sql( - "coalesce(\($defaultOwnerName()), (\(parentMetadata.select(\.ownerName))), \(ownerName))" + "coalesce(\($currentOwnerName()), (\(parentMetadata.select(\.ownerName))), \(defaultOwnerName))" ) ) } ?? ( nil, nil, - #sql("coalesce(\($defaultZoneName()), \(zoneName))"), - #sql("coalesce(\($defaultOwnerName()), \(ownerName))") + #sql("coalesce(\($currentZoneName()), \(defaultZoneName))"), + #sql("coalesce(\($currentOwnerName()), \(defaultOwnerName))") ) } @@ -400,13 +392,15 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) private func descendantRecordNames( recordName: some QueryExpression, - includeRecord: Bool, + includeSelf: Bool, select: (Where) -> Select ) -> some Statement { With { SyncMetadata .where { $0.recordName.eq(recordName) } - .select { DescendantMetadata.Columns(recordName: $0.recordName, parentRecordName: #bind(nil)) } + .select { + DescendantMetadata.Columns(recordName: $0.recordName, parentRecordName: #bind(nil)) + } .union( all: true, SyncMetadata @@ -421,7 +415,7 @@ } query: { select( DescendantMetadata.where { - if !includeRecord { + if !includeSelf { $0.recordName.neq(recordName) } } @@ -481,14 +475,14 @@ } @Table @Selection - struct DescendantMetadata { + private struct DescendantMetadata { let recordName: String let parentRecordName: String? } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Table @Selection - struct AncestorMetadata { + private struct AncestorMetadata { let recordName: String let parentRecordName: String? @Column(as: CKRecord?.SystemFieldsRepresentation.self) @@ -509,7 +503,7 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Table @Selection - struct RootShare { + private struct RootShare { let parentRecordName: String? @Column(as: CKShare?.SystemFieldsRepresentation.self) let share: CKShare? diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index c7a76fe8..f70f2e8d 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -330,8 +330,8 @@ db.add(function: $didUpdate) db.add(function: $didDelete) db.add(function: $hasPermission) - db.add(function: $defaultZoneName) - db.add(function: $defaultOwnerName) + db.add(function: $currentZoneName) + db.add(function: $currentOwnerName) for trigger in SyncMetadata.callbackTriggers(for: self) { try trigger.execute(db) @@ -614,24 +614,26 @@ ownerName: String, oldZoneName: String, oldOwnerName: String, - childRecordNames: [String]? + descendantRecordNames: [String]? ) { var oldChanges: [CKSyncEngine.PendingRecordZoneChange] = [] var newChanges: [CKSyncEngine.PendingRecordZoneChange] = [] let oldZoneID = CKRecordZone.ID(zoneName: oldZoneName, ownerName: oldOwnerName) - let zoneID = CKRecordZone.ID.init(zoneName: zoneName, ownerName: ownerName) + let zoneID = CKRecordZone.ID(zoneName: zoneName, ownerName: ownerName) if oldZoneID != zoneID { oldChanges.append(.deleteRecord(CKRecord.ID(recordName: recordName, zoneID: oldZoneID))) - for childRecordName in childRecordNames ?? [] { + for descendantRecordName in descendantRecordNames ?? [] { oldChanges.append( - .deleteRecord(CKRecord.ID(recordName: childRecordName, zoneID: oldZoneID)) + .deleteRecord(CKRecord.ID(recordName: descendantRecordName, zoneID: oldZoneID)) ) } newChanges.append(.saveRecord(CKRecord.ID(recordName: recordName, zoneID: zoneID))) - for childRecordName in childRecordNames ?? [] { - newChanges.append(.saveRecord(CKRecord.ID(recordName: childRecordName, zoneID: zoneID))) + for descendantRecordName in descendantRecordNames ?? [] { + newChanges.append( + .saveRecord(CKRecord.ID(recordName: descendantRecordName, zoneID: zoneID)) + ) } } else { newChanges.append( @@ -643,6 +645,7 @@ ) ) } + guard isRunning else { // TODO: can this be done in the trigger?? Task { [changes = oldChanges + newChanges] in @@ -1006,12 +1009,14 @@ 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 .findAll(deletedRecordIDs) @@ -1049,8 +1054,6 @@ ) } query: { RecordWithRoot - // TODO: look into this - .where { $0.recordName.in(deletedRecordIDs.map(\.recordName)) } .select { ($0.lastKnownServerRecord, $0.rootLastKnownServerRecord) } } .fetchAll(db) @@ -1247,9 +1250,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 { @@ -1532,20 +1535,23 @@ } } - func deleteShare(recordID: CKRecord.ID) async throws { + func deleteShare(shareRecordID: CKRecord.ID) async throws { try await userDatabase.write { db in - let shareAndLastKnownServerRecord = + let shareAndRecordNameAndZone = try SyncMetadata .where(\.isShared) - .select { ($0.share, $0.lastKnownServerRecord) } + .select { ($0.share, $0.recordName, $0.zoneName, $0.ownerName) } .fetchAll(db) - .first(where: { share, _ in share?.recordID == recordID }) ?? nil - guard - let (_, lastKnownServerRecord) = shareAndLastKnownServerRecord, - let lastKnownServerRecord + .first(where: { share, _, _, _ in share?.recordID == shareRecordID }) ?? nil + guard let (_, recordName, zoneName, ownerName) = shareAndRecordNameAndZone else { return } try SyncMetadata - .find(lastKnownServerRecord.recordID) + .find( + CKRecord.ID( + recordName: recordName, + zoneID: CKRecordZone.ID(zoneName: zoneName, ownerName: ownerName) + ) + ) .update { $0.share = nil } .execute(db) } @@ -1623,7 +1629,7 @@ } do { - try $_defaultZoneID.withValue(serverRecord.recordID.zoneID) { + 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) @@ -2117,15 +2123,15 @@ @TaskLocal package var _isSynchronizingChanges = false @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @TaskLocal package var _defaultZoneID: CKRecordZone.ID? + @TaskLocal package var _currentZoneID: CKRecordZone.ID? @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @DatabaseFunction - func defaultZoneName() -> String? { - _defaultZoneID?.zoneName + @DatabaseFunction("sqlitedata_icloud_currentZoneName") + func currentZoneName() -> String? { + _currentZoneID?.zoneName } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @DatabaseFunction - func defaultOwnerName() -> String? { - _defaultZoneID?.ownerName + @DatabaseFunction("sqlitedata_icloud_currentOwnerName") + func currentOwnerName() -> String? { + _currentZoneID?.ownerName } #endif 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/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index 805d3687..c60f6e6e 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -1024,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) } } @@ -1060,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, @@ -1074,6 +1085,63 @@ } } + + +// /// 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 diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index ba29ae03..0ce6e493 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -66,9 +66,7 @@ ) SELECT "descendantMetadatas"."recordName" FROM "descendantMetadatas"))); - SELECT "sqlitedata_icloud_didUpdate"("new"."recordName", "new"."zoneName", "new"."ownerName", "old"."zoneName", "old"."ownerName", iif( - (("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")), - ( + 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" @@ -81,9 +79,7 @@ SELECT json_group_array("descendantMetadatas"."recordName") FROM "descendantMetadatas" WHERE ("descendantMetadatas"."recordName" <> "new"."recordName") - ), - NULL - )); + ) END); END """, [3]: """ @@ -452,9 +448,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'childWithOnDeleteSetDefaults', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), 'zone'), 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") @@ -479,9 +475,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'childWithOnDeleteSetNulls', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), 'zone'), 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") @@ -506,7 +502,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'modelAs', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."id", 'modelAs', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -529,9 +525,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'modelBs', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs'))), 'zone'), 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") @@ -556,9 +552,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'modelCs', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs'))), 'zone'), 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") @@ -583,7 +579,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'parents', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."id", 'parents', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -606,7 +602,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'reminderTags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."id", 'reminderTags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -629,9 +625,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'reminders', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), 'zone'), 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") @@ -656,9 +652,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'remindersListAssets', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), 'zone'), 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") @@ -683,9 +679,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'remindersListPrivates', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), 'zone'), 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") @@ -710,7 +706,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'remindersLists', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."id", 'remindersLists', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -733,7 +729,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."title", 'tags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."title", 'tags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1008,9 +1004,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'childWithOnDeleteSetDefaults', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), 'zone'), 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") @@ -1035,9 +1031,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'childWithOnDeleteSetNulls', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."parentID") AND ("sqlitedata_icloud_metadata"."recordType" = 'parents'))), 'zone'), 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") @@ -1062,7 +1058,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'modelAs', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."id", 'modelAs', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1085,9 +1081,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'modelBs', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelAID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelAs'))), 'zone'), 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") @@ -1112,9 +1108,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'modelCs', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."modelBID") AND ("sqlitedata_icloud_metadata"."recordType" = 'modelBs'))), 'zone'), 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") @@ -1139,7 +1135,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'parents', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."id", 'parents', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1162,7 +1158,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'reminderTags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."id", 'reminderTags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1185,9 +1181,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'reminders', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), 'zone'), 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") @@ -1212,9 +1208,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'remindersListAssets', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), 'zone'), 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") @@ -1239,9 +1235,9 @@ 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("defaultZoneName"(), (SELECT "sqlitedata_icloud_metadata"."zoneName" + SELECT "new"."id", 'remindersListPrivates', 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("defaultOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + WHERE (("sqlitedata_icloud_metadata"."recordPrimaryKey" = "new"."remindersListID") AND ("sqlitedata_icloud_metadata"."recordType" = 'remindersLists'))), 'zone'), 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") @@ -1266,7 +1262,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."id", 'remindersLists', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."id", 'remindersLists', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END @@ -1289,7 +1285,7 @@ WHERE ((NOT ("sqlitedata_icloud_syncEngineIsSynchronizingChanges"()) AND ("rootShares"."parentRecordName" IS NULL)) AND NOT ("sqlitedata_icloud_hasPermission"("rootShares"."share"))); INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") - SELECT "new"."title", 'tags', coalesce("defaultZoneName"(), 'zone'), coalesce("defaultOwnerName"(), '__defaultOwner__'), NULL, NULL + SELECT "new"."title", 'tags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL ON CONFLICT ("recordPrimaryKey", "recordType") DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; END From cc5b0af77dab99f2e4e6aef56df1ef4c08e36bbf Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 12:25:13 -0500 Subject: [PATCH 15/25] wip --- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3e56ff26..13fe0977 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -154,6 +154,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", From 17af59984c307f7c1bfb4a098e1d988c1ce149e4 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 12:27:18 -0500 Subject: [PATCH 16/25] wip --- Sources/SQLiteData/CloudKit/CloudKitSharing.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLiteData/CloudKit/CloudKitSharing.swift b/Sources/SQLiteData/CloudKit/CloudKitSharing.swift index eac7c4a3..772e38b7 100644 --- a/Sources/SQLiteData/CloudKit/CloudKitSharing.swift +++ b/Sources/SQLiteData/CloudKit/CloudKitSharing.swift @@ -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() From 06733a00dd50d6eb9057e795c2df46ecb5a9a42d Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 13:50:01 -0500 Subject: [PATCH 17/25] wip --- .../CloudKit/Internal/Triggers.swift | 100 ++++--- Sources/SQLiteData/CloudKit/SyncEngine.swift | 7 + .../CloudKitTests/SharingTests.swift | 230 ++++++++++++++++ .../CloudKitTests/TriggerTests.swift | 246 ++++++++++-------- 4 files changed, 439 insertions(+), 144 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index 5c9f7658..8fe949b5 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,8 @@ @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 +149,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,23 +170,35 @@ Values( #sql("\(new.primaryKey)"), T.tableName, - zoneName, - ownerName, + zoneName ?? defaultZoneName, + ownerName ?? defaultOwnerName, parentRecordPrimaryKey, parentRecordType ) - } onConflict: { - ($0.recordPrimaryKey, $0.recordType) - } doUpdate: { - $0.zoneName = Case($1.zoneName) - .when(defaultZone.zoneID.zoneName, then: $0.zoneName) - .else($1.zoneName) - $0.ownerName = Case($1.ownerName) - .when(defaultZone.zoneID.ownerName, then: $0.ownerName) - .else($1.ownerName) - $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() } } } @@ -233,16 +259,6 @@ } validate(recordName: new.recordName) - SyncMetadata - .where { - zoneChanged && $0.recordName.in(selfAndDescendantRecordNames) - } - .update { - $0.zoneName = new.zoneName - $0.ownerName = new.ownerName - $0.lastKnownServerRecord = nil - $0._lastKnownServerRecordAllFields = nil - } Values( syncEngine.$didUpdate( recordName: new.recordName, @@ -253,6 +269,16 @@ descendantRecordNames: Case().when(zoneChanged, then: descendantRecordNamesJSON) ) ) + SyncMetadata + .where { + zoneChanged && $0.recordName.in(selfAndDescendantRecordNames) + } + .update { + $0.zoneName = new.zoneName + $0.ownerName = new.ownerName + $0.lastKnownServerRecord = nil + $0._lastKnownServerRecordAllFields = nil + } } when: { old, new in old._isDeleted.eq(new._isDeleted) && !SyncEngine.isSynchronizingChanges() } @@ -289,17 +315,9 @@ ) -> ( parentRecordPrimaryKey: SQLQueryExpression?, parentRecordType: SQLQueryExpression?, - zoneName: SQLQueryExpression, - ownerName: SQLQueryExpression + zoneName: SQLQueryExpression, + ownerName: SQLQueryExpression ) { - 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 parentForeignKey .map { foreignKey in @@ -318,18 +336,18 @@ parentRecordPrimaryKey, parentRecordType, #sql( - "coalesce(\($currentZoneName()), (\(parentMetadata.select(\.zoneName))), \(defaultZoneName))" + "coalesce(\($currentZoneName()), (\(parentMetadata.select(\.zoneName))))" ), #sql( - "coalesce(\($currentOwnerName()), (\(parentMetadata.select(\.ownerName))), \(defaultOwnerName))" + "coalesce(\($currentOwnerName()), (\(parentMetadata.select(\.ownerName))))" ) ) } ?? ( nil, nil, - #sql("coalesce(\($currentZoneName()), \(defaultZoneName))"), - #sql("coalesce(\($currentOwnerName()), \(defaultOwnerName))") + #sql("\($currentZoneName())"), + #sql("\($currentOwnerName())") ) } diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index f70f2e8d..ef292cc7 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -332,6 +332,7 @@ db.add(function: $hasPermission) db.add(function: $currentZoneName) db.add(function: $currentOwnerName) + db.add(function: $SQLDump) for trigger in SyncMetadata.callbackTriggers(for: self) { try trigger.execute(db) @@ -2134,4 +2135,10 @@ func currentOwnerName() -> String? { _currentZoneID?.ownerName } + +@DatabaseFunction +func SQLDump(_ value: String?) -> String? { + customDump(value, name: "SQLDump") + return value +} #endif diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index c60f6e6e..69905901 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -1414,6 +1414,236 @@ } } + @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 { diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index 0ce6e493..d40b38d4 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -53,19 +53,6 @@ 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"))); - UPDATE "sqlitedata_icloud_metadata" - SET "zoneName" = "new"."zoneName", "ownerName" = "new"."ownerName", "lastKnownServerRecord" = NULL, "_lastKnownServerRecordAllFields" = NULL - WHERE ((("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")) AND ("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"))); 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" @@ -80,6 +67,19 @@ FROM "descendantMetadatas" WHERE ("descendantMetadatas"."recordName" <> "new"."recordName") ) END); + UPDATE "sqlitedata_icloud_metadata" + SET "zoneName" = "new"."zoneName", "ownerName" = "new"."ownerName", "lastKnownServerRecord" = NULL, "_lastKnownServerRecordAllFields" = NULL + WHERE ((("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")) AND ("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 """, [3]: """ @@ -448,13 +448,12 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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]: """ @@ -475,13 +474,12 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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]: """ @@ -503,8 +501,7 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'modelAs', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + ON CONFLICT DO NOTHING; END """, [30]: """ @@ -525,13 +522,12 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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]: """ @@ -552,13 +548,12 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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]: """ @@ -580,8 +575,7 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'parents', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + ON CONFLICT DO NOTHING; END """, [33]: """ @@ -603,8 +597,7 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'reminderTags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + ON CONFLICT DO NOTHING; END """, [34]: """ @@ -625,13 +618,12 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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]: """ @@ -652,13 +644,12 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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]: """ @@ -679,13 +670,12 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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]: """ @@ -707,8 +697,7 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'remindersLists', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + ON CONFLICT DO NOTHING; END """, [38]: """ @@ -730,8 +719,7 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."title", 'tags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + ON CONFLICT DO NOTHING; END """, [39]: """ @@ -1004,13 +992,19 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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]: """ @@ -1031,13 +1025,19 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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" = 'childWithOnDeleteSetNulls')); END """, [53]: """ @@ -1059,8 +1059,10 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'modelAs', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + 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]: """ @@ -1081,13 +1083,19 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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; + 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')))), "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')))), "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]: """ @@ -1108,13 +1116,19 @@ 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("sqlitedata_icloud_currentZoneName"(), (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(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'))), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + 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'))), '__defaultOwner__'), "new"."modelBID", 'modelBs' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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"."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]: """ @@ -1136,8 +1150,10 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'parents', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + 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]: """ @@ -1159,8 +1175,10 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'reminderTags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + 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]: """ @@ -1181,13 +1199,19 @@ 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("sqlitedata_icloud_currentZoneName"(), (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("sqlitedata_icloud_currentOwnerName"(), (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 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'))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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"."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" = 'reminders')); END """, [59]: """ @@ -1208,13 +1232,19 @@ 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("sqlitedata_icloud_currentZoneName"(), (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(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'))), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), (SELECT "sqlitedata_icloud_metadata"."ownerName" + 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'))), '__defaultOwner__'), "new"."remindersListID", 'remindersLists' - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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"."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]: """ @@ -1235,13 +1265,19 @@ 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("sqlitedata_icloud_currentZoneName"(), (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(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("sqlitedata_icloud_currentOwnerName"(), (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 "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "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" = 'remindersListPrivates')); END """, [61]: """ @@ -1263,8 +1299,10 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."id", 'remindersLists', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + 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 """, [62]: """ @@ -1286,8 +1324,10 @@ INSERT INTO "sqlitedata_icloud_metadata" ("recordPrimaryKey", "recordType", "zoneName", "ownerName", "parentRecordPrimaryKey", "parentRecordType") SELECT "new"."title", 'tags', coalesce("sqlitedata_icloud_currentZoneName"(), 'zone'), coalesce("sqlitedata_icloud_currentOwnerName"(), '__defaultOwner__'), NULL, NULL - ON CONFLICT ("recordPrimaryKey", "recordType") - DO UPDATE SET "zoneName" = CASE "excluded"."zoneName" WHEN 'zone' THEN "sqlitedata_icloud_metadata"."zoneName" ELSE "excluded"."zoneName" END, "ownerName" = CASE "excluded"."ownerName" WHEN '__defaultOwner__' THEN "sqlitedata_icloud_metadata"."ownerName" ELSE "excluded"."ownerName" END, "parentRecordPrimaryKey" = "excluded"."parentRecordPrimaryKey", "parentRecordType" = "excluded"."parentRecordType", "userModificationTime" = "excluded"."userModificationTime"; + 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 """ ] From b9070915efea8776c66dc124773d90fde7367b53 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 15:34:48 -0500 Subject: [PATCH 18/25] support moving shares between zones and receiving saves before deletes --- .../CloudKit/Internal/Triggers.swift | 49 ++- Sources/SQLiteData/CloudKit/SyncEngine.swift | 34 +- .../CloudKitTests/SharingTests.swift | 409 +++++++++++++++--- .../CloudKitTests/TriggerTests.swift | 250 +++++------ .../Internal/BaseCloudKitTests.swift | 2 +- 5 files changed, 536 insertions(+), 208 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index 8fe949b5..b0f37b4e 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -208,17 +208,16 @@ 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) @@ -238,19 +237,43 @@ ) } - fileprivate static func afterUpdateTrigger(for syncEngine: SyncEngine) -> TemporaryTrigger + fileprivate static func afterZoneUpdateTrigger() -> TemporaryTrigger { createTemporaryTrigger( - "after_update_on_sqlitedata_icloud_metadata", + "\(String.sqliteDataCloudKitSchemaName)_after_zone_update_on_sqlitedata_icloud_metadata", ifNotExists: true, - after: .update { old, new in - let zoneChanged = new.zoneName.neq(old.zoneName) || new.ownerName.neq(old.ownerName) + 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 = #sql("SQLDump(\(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( + "\(String.sqliteDataCloudKitSchemaName)_after_update_on_sqlitedata_icloud_metadata", + ifNotExists: true, + 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 @@ -269,16 +292,6 @@ descendantRecordNames: Case().when(zoneChanged, then: descendantRecordNamesJSON) ) ) - SyncMetadata - .where { - zoneChanged && $0.recordName.in(selfAndDescendantRecordNames) - } - .update { - $0.zoneName = new.zoneName - $0.ownerName = new.ownerName - $0.lastKnownServerRecord = nil - $0._lastKnownServerRecordAllFields = nil - } } when: { old, new in old._isDeleted.eq(new._isDeleted) && !SyncEngine.isSynchronizingChanges() } @@ -289,7 +302,7 @@ Self > { createTemporaryTrigger( - "after_delete_on_sqlitedata_icloud_metadata", + "\(String.sqliteDataCloudKitSchemaName)_after_delete_on_sqlitedata_icloud_metadata", ifNotExists: true, after: .update(of: \._isDeleted) { _, new in Values( diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index ef292cc7..ffb0da0f 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -332,7 +332,10 @@ db.add(function: $hasPermission) db.add(function: $currentZoneName) db.add(function: $currentOwnerName) + #if DEBUG db.add(function: $SQLDump) + db.add(function: $SQLFatalError) + #endif for trigger in SyncMetadata.callbackTriggers(for: self) { try trigger.execute(db) @@ -1228,15 +1231,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() @@ -1578,7 +1581,7 @@ guard let recordPrimaryKey = serverRecord.recordID.recordPrimaryKey else { return } - let metadata = try SyncMetadata.insert { + try SyncMetadata.insert { SyncMetadata( recordPrimaryKey: recordPrimaryKey, recordType: serverRecord.recordType, @@ -1599,23 +1602,32 @@ } else { // NB: Keep this to allow for "RETURNING *" to work below: $0.recordPrimaryKey = $0.recordPrimaryKey + $0.zoneName = serverRecord.recordID.zoneID.zoneName + $0.ownerName = serverRecord.recordID.zoneID.ownerName } } - .returning(\.self) - .fetchOne(db) + .execute(db) + let metadata = try SyncMetadata.find(serverRecord.recordID).fetchOne(db) + + guard + let metadata +// metadata.zoneName == serverRecord.recordID.zoneID.zoneName, +// metadata.ownerName == serverRecord.recordID.zoneID.ownerName + else { + print("!!!") + return + } guard let table = tablesByName[serverRecord.recordType] else { return } - serverRecord.userModificationTime = - metadata?.userModificationTime ?? serverRecord.userModificationTime + serverRecord.userModificationTime = metadata.userModificationTime func open(_: T.Type) throws { var columnNames: [String] = T.TableColumns.writableColumns.map(\.name) if !force, - let metadata, let allFields = metadata._lastKnownServerRecordAllFields, let row = try T.find(#sql("\(bind: metadata.recordPrimaryKey)")).fetchOne(db) { @@ -2141,4 +2153,8 @@ func SQLDump(_ value: String?) -> String? { customDump(value, name: "SQLDump") return value } +@DatabaseFunction +func SQLFatalError() { + fatalError() +} #endif diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index 69905901..d273b0e3 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -1085,62 +1085,60 @@ } } - - -// /// 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) -// } + // /// 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 { @@ -1194,7 +1192,7 @@ 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) } @@ -1414,6 +1412,300 @@ } } + @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 freshModelA2Record = try syncEngine.shared.database.record(for: ModelA.recordID(for: 2)) + let movedModelBRecord = CKRecord.init( + 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.init( + 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 @@ -1453,7 +1745,8 @@ modelBRecord.setValue(1, forKey: "modelAID", at: now) modelBRecord.parent = CKRecord.Reference(record: modelARecord, action: .none) - _ = try syncEngine + _ = + 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) diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index d40b38d4..dab01c06 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -19,70 +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", "new"."zoneName", "new"."ownerName", "new"."zoneName", "new"."ownerName", NULL); - 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", "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); - UPDATE "sqlitedata_icloud_metadata" - SET "zoneName" = "new"."zoneName", "ownerName" = "new"."ownerName", "lastKnownServerRecord" = NULL, "_lastKnownServerRecordAllFields" = NULL - WHERE ((("new"."zoneName" <> "old"."zoneName") OR ("new"."ownerName" <> "old"."ownerName")) AND ("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 - """, - [3]: """ CREATE TRIGGER "sqlitedata_icloud_after_delete_on_childWithOnDeleteSetDefaults_from_sync_engine" AFTER DELETE ON "childWithOnDeleteSetDefaults" FOR EACH ROW WHEN "sqlitedata_icloud_syncEngineIsSynchronizingChanges"() BEGIN @@ -90,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 @@ -111,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 @@ -119,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 @@ -140,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 @@ -148,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 @@ -169,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 @@ -177,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 @@ -198,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 @@ -206,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 @@ -227,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 @@ -235,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 @@ -256,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 @@ -264,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 @@ -285,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 @@ -293,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 @@ -314,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 @@ -322,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 @@ -343,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 @@ -351,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 @@ -372,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 @@ -380,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 @@ -401,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 @@ -409,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 @@ -430,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,7 +412,7 @@ ON CONFLICT DO NOTHING; END """, - [28]: """ + [26]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_childWithOnDeleteSetNulls" AFTER INSERT ON "childWithOnDeleteSetNulls" FOR EACH ROW BEGIN @@ -482,7 +438,7 @@ ON CONFLICT DO NOTHING; END """, - [29]: """ + [27]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_modelAs" AFTER INSERT ON "modelAs" FOR EACH ROW BEGIN @@ -504,7 +460,7 @@ ON CONFLICT DO NOTHING; END """, - [30]: """ + [28]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_modelBs" AFTER INSERT ON "modelBs" FOR EACH ROW BEGIN @@ -530,7 +486,7 @@ ON CONFLICT DO NOTHING; END """, - [31]: """ + [29]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_modelCs" AFTER INSERT ON "modelCs" FOR EACH ROW BEGIN @@ -556,7 +512,7 @@ ON CONFLICT DO NOTHING; END """, - [32]: """ + [30]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_parents" AFTER INSERT ON "parents" FOR EACH ROW BEGIN @@ -578,7 +534,7 @@ ON CONFLICT DO NOTHING; END """, - [33]: """ + [31]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_reminderTags" AFTER INSERT ON "reminderTags" FOR EACH ROW BEGIN @@ -600,7 +556,7 @@ ON CONFLICT DO NOTHING; END """, - [34]: """ + [32]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_reminders" AFTER INSERT ON "reminders" FOR EACH ROW BEGIN @@ -626,7 +582,7 @@ ON CONFLICT DO NOTHING; END """, - [35]: """ + [33]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_remindersListAssets" AFTER INSERT ON "remindersListAssets" FOR EACH ROW BEGIN @@ -652,7 +608,7 @@ ON CONFLICT DO NOTHING; END """, - [36]: """ + [34]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_remindersListPrivates" AFTER INSERT ON "remindersListPrivates" FOR EACH ROW BEGIN @@ -678,7 +634,7 @@ ON CONFLICT DO NOTHING; END """, - [37]: """ + [35]: """ CREATE TRIGGER "sqlitedata_icloud_after_insert_on_remindersLists" AFTER INSERT ON "remindersLists" FOR EACH ROW BEGIN @@ -700,7 +656,16 @@ 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 @@ -722,7 +687,7 @@ 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 @@ -743,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 @@ -764,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 @@ -785,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 @@ -806,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 @@ -827,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 @@ -848,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 @@ -869,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 @@ -890,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 @@ -911,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 @@ -932,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 @@ -953,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 @@ -974,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 @@ -1007,7 +972,7 @@ 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 @@ -1040,7 +1005,7 @@ 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 @@ -1065,7 +1030,7 @@ 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 @@ -1098,7 +1063,7 @@ 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 @@ -1131,7 +1096,7 @@ 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 @@ -1156,7 +1121,7 @@ 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 @@ -1181,7 +1146,7 @@ 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 @@ -1214,7 +1179,7 @@ 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 @@ -1247,7 +1212,7 @@ 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 @@ -1280,7 +1245,7 @@ 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 @@ -1305,6 +1270,28 @@ 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]: """ CREATE TRIGGER "sqlitedata_icloud_after_update_on_tags" AFTER UPDATE ON "tags" @@ -1329,6 +1316,25 @@ 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" = SQLDump("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() From 806be271a9f932ee89f39ed89e706c40fe04751d Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 15:36:03 -0500 Subject: [PATCH 19/25] wip --- .../CloudKitTests/SharingTests.swift | 294 +++++++++++++++++- 1 file changed, 293 insertions(+), 1 deletion(-) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index d273b0e3..6c022f54 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -1412,6 +1412,299 @@ } } + @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.init( + 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.init( + 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 @@ -1458,7 +1751,6 @@ ) ) - //let freshModelA2Record = try syncEngine.shared.database.record(for: ModelA.recordID(for: 2)) let movedModelBRecord = CKRecord.init( recordType: ModelB.tableName, recordID: ModelB.recordID(for: 1, zoneID: externalZone.zoneID) From 7d3164ead74e70d41fb1786cce44faa519a237fd Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 16 Sep 2025 13:37:12 -0700 Subject: [PATCH 20/25] Update Sources/SQLiteData/CloudKit/Internal/Triggers.swift --- Sources/SQLiteData/CloudKit/Internal/Triggers.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index b0f37b4e..e316e614 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -138,7 +138,6 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) extension SyncMetadata { - fileprivate static func insert( new: StructuredQueriesCore.TableAlias.TableColumns, parentForeignKey: ForeignKey?, From ff78e6c8c9c66b641c8b0f2ec392309fd476278f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 16 Sep 2025 13:41:07 -0700 Subject: [PATCH 21/25] wip --- .../xcshareddata/swiftpm/Package.resolved | 24 ++------------ Examples/Reminders/TagsForm.swift | 2 +- .../CloudKit/Internal/Metadatabase.swift | 2 +- .../Internal/MockCloudContainer.swift | 2 +- .../CloudKit/Internal/Triggers.swift | 31 +++++++------------ Sources/SQLiteData/CloudKit/SyncEngine.swift | 4 +-- .../CloudKitTests/SharingTests.swift | 8 ++--- .../Internal/PrintTimestampsScope.swift | 4 +-- .../SQLiteDataTests/Internal/TestScopes.swift | 12 +++---- 9 files changed, 32 insertions(+), 57 deletions(-) diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 13fe0977..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" } }, { 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/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/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index e316e614..b16d5899 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -236,8 +236,7 @@ ) } - fileprivate static func afterZoneUpdateTrigger() -> TemporaryTrigger - { + fileprivate static func afterZoneUpdateTrigger() -> TemporaryTrigger { createTemporaryTrigger( "\(String.sqliteDataCloudKitSchemaName)_after_zone_update_on_sqlitedata_icloud_metadata", ifNotExists: true, @@ -297,9 +296,9 @@ ) } - fileprivate static func afterSoftDeleteTrigger(for syncEngine: SyncEngine) -> TemporaryTrigger< - Self - > { + fileprivate static func afterSoftDeleteTrigger( + for syncEngine: SyncEngine + ) -> TemporaryTrigger { createTemporaryTrigger( "\(String.sqliteDataCloudKitSchemaName)_after_delete_on_sqlitedata_icloud_metadata", ifNotExists: true, @@ -338,28 +337,22 @@ 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(\($currentZoneName()), (\(parentMetadata.select(\.zoneName))))" - ), - #sql( - "coalesce(\($currentOwnerName()), (\(parentMetadata.select(\.ownerName))))" - ) + #sql("coalesce(\($currentZoneName()), (\(parentMetadata.select(\.zoneName))))"), + #sql("coalesce(\($currentOwnerName()), (\(parentMetadata.select(\.ownerName))))") ) } ?? ( nil, nil, - #sql("\($currentZoneName())"), - #sql("\($currentOwnerName())") + SQLQueryExpression($currentZoneName()), + SQLQueryExpression($currentOwnerName()) ) } diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index ffb0da0f..f378ca59 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -1768,7 +1768,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...] @@ -1778,7 +1778,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[.. Self { - .init(printTimestamps) + Self(printTimestamps) } } #endif 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 From 15325f062c36b83faee1ec91d36748400aa1423f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 16 Sep 2025 13:43:04 -0700 Subject: [PATCH 22/25] wip --- .../CloudKit/Internal/Triggers.swift | 2 +- Sources/SQLiteData/CloudKit/SyncEngine.swift | 26 ++----------------- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift index b16d5899..16077e14 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Triggers.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Triggers.swift @@ -255,7 +255,7 @@ } .update { $0.zoneName = new.zoneName - $0.ownerName = #sql("SQLDump(\(new.ownerName))") + $0.ownerName = new.ownerName $0.lastKnownServerRecord = nil $0._lastKnownServerRecordAllFields = nil } diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index f378ca59..80839df5 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -332,10 +332,6 @@ db.add(function: $hasPermission) db.add(function: $currentZoneName) db.add(function: $currentOwnerName) - #if DEBUG - db.add(function: $SQLDump) - db.add(function: $SQLFatalError) - #endif for trigger in SyncMetadata.callbackTriggers(for: self) { try trigger.execute(db) @@ -1607,18 +1603,10 @@ } } .execute(db) - let metadata = try SyncMetadata.find(serverRecord.recordID).fetchOne(db) guard - let metadata -// metadata.zoneName == serverRecord.recordID.zoneID.zoneName, -// metadata.ownerName == serverRecord.recordID.zoneID.ownerName - else { - print("!!!") - return - } - - guard let table = tablesByName[serverRecord.recordType] + let metadata = try SyncMetadata.find(serverRecord.recordID).fetchOne(db), + let table = tablesByName[serverRecord.recordType] else { return } @@ -2147,14 +2135,4 @@ func currentOwnerName() -> String? { _currentZoneID?.ownerName } - -@DatabaseFunction -func SQLDump(_ value: String?) -> String? { - customDump(value, name: "SQLDump") - return value -} -@DatabaseFunction -func SQLFatalError() { - fatalError() -} #endif From 653ca22cabeb2f4a06d93c87f73877f4521e712e Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 15:45:30 -0500 Subject: [PATCH 23/25] wip --- Sources/SQLiteData/CloudKit/SyncEngine.swift | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 80839df5..cc101c2a 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -637,17 +637,12 @@ } } else { newChanges.append( - .saveRecord( - CKRecord.ID( - recordName: recordName, - zoneID: zoneID - ) - ) + .saveRecord(CKRecord.ID(recordName: recordName, zoneID: zoneID)) ) } guard isRunning else { - // TODO: can this be done in the trigger?? + // 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 @@ -1596,8 +1591,6 @@ if tablesByName[serverRecord.recordType] == nil { $0.setLastKnownServerRecord(serverRecord) } else { - // NB: Keep this to allow for "RETURNING *" to work below: - $0.recordPrimaryKey = $0.recordPrimaryKey $0.zoneName = serverRecord.recordID.zoneID.zoneName $0.ownerName = serverRecord.recordID.zoneID.ownerName } From 861646b8f99dc9b47c168b9e81a6f32549925148 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 16 Sep 2025 13:47:01 -0700 Subject: [PATCH 24/25] format --- Package.swift | 2 +- README.md | 6 +++--- Sources/SQLiteData/CloudKit/CloudKitSharing.swift | 2 +- .../CloudKit/Internal/MockSyncEngine.swift | 3 ++- Sources/SQLiteData/CloudKit/SyncMetadata.swift | 6 +++--- .../Documentation.docc/Articles/CloudKit.md | 2 +- .../Documentation.docc/Articles/Observing.md | 4 ++-- .../Documentation.docc/Articles/PreparingDatabase.md | 12 ++++++------ Sources/SQLiteData/Documentation.docc/SQLiteData.md | 2 +- .../CloudKitTests/AccountLifecycleTests.swift | 3 ++- .../CloudKitTests/AppLifecycleTests.swift | 6 ++++-- .../CloudKitTests/MockCloudDatabaseTests.swift | 3 ++- .../CloudKitTests/RecordTypeTests.swift | 2 +- .../SQLiteDataTests/CloudKitTests/SharingTests.swift | 8 ++++++-- Tests/SQLiteDataTests/Internal/Schema.swift | 6 +++--- 15 files changed, 38 insertions(+), 29 deletions(-) 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 772e38b7..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) 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/SyncMetadata.swift b/Sources/SQLiteData/CloudKit/SyncMetadata.swift index 78a99930..44672565 100644 --- a/Sources/SQLiteData/CloudKit/SyncMetadata.swift +++ b/Sources/SQLiteData/CloudKit/SyncMetadata.swift @@ -122,8 +122,8 @@ 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) + && $0.zoneName.eq(recordID.zoneID.zoneName) + && $0.ownerName.eq(recordID.zoneID.ownerName) } } @@ -131,7 +131,7 @@ let condition: QueryFragment = recordIDs.map { "(\(bind: $0.recordName), \(bind: $0.zoneID.zoneName), \(bind: $0.zoneID.ownerName))" } - .joined(separator: ", ") + .joined(separator: ", ") return Self.where { #sql("(\($0.recordName), \($0.zoneName), \($0.ownerName)) IN (\(condition))") } 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/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/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/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 d08ff319..46f6f4a9 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -1413,7 +1413,9 @@ } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @Test func movesChildRecordFromPrivateParentToSharedParent_ReceiveDeleteBeforeSave() async throws { + @Test func movesChildRecordFromPrivateParentToSharedParent_ReceiveDeleteBeforeSave() + async throws + { try await userDatabase.userWrite { db in try db.seed { ModelA.Draft(id: 1, count: 42) @@ -1706,7 +1708,9 @@ } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @Test func movesChildRecordFromPrivateParentToSharedParent_ReceiveSaveBeforeDelete() async throws { + @Test func movesChildRecordFromPrivateParentToSharedParent_ReceiveSaveBeforeDelete() + async throws + { try await userDatabase.userWrite { db in try db.seed { ModelA.Draft(id: 1, count: 42) diff --git a/Tests/SQLiteDataTests/Internal/Schema.swift b/Tests/SQLiteDataTests/Internal/Schema.swift index 997a49e9..3d11f0a7 100644 --- a/Tests/SQLiteDataTests/Internal/Schema.swift +++ b/Tests/SQLiteDataTests/Internal/Schema.swift @@ -82,9 +82,9 @@ func database( if attachMetadatabase { try db.attachMetadatabase(containerIdentifier: containerIdentifier) } -// db.trace { -// print($0.expandedDescription) -// } + // db.trace { + // print($0.expandedDescription) + // } } let url = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).sqlite") let database = try DatabasePool(path: url.path(), configuration: configuration) From 2cecccba20f1c46001baf5d97f5f3d448904371e Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 16 Sep 2025 15:47:52 -0500 Subject: [PATCH 25/25] wip; --- Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift | 1 + Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index 46f6f4a9..9b4c2f54 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -1085,6 +1085,7 @@ } } + // 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, *) diff --git a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift index dab01c06..d7ef5e19 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/TriggerTests.swift @@ -1322,7 +1322,7 @@ 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" = SQLDump("new"."ownerName"), "lastKnownServerRecord" = NULL, "_lastKnownServerRecordAllFields" = NULL + 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"