From 48828fdca3210d65c023a3b05c0b7be1e2d576ba Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 9 May 2017 17:58:41 +0900 Subject: [PATCH] Custom migrations! --- .../CustomSchemaMappingProvider.swift | 459 +++++++++--------- 1 file changed, 227 insertions(+), 232 deletions(-) diff --git a/Sources/Migrating/Schema Mapping Providers/CustomSchemaMappingProvider.swift b/Sources/Migrating/Schema Mapping Providers/CustomSchemaMappingProvider.swift index ced7354..9dda317 100644 --- a/Sources/Migrating/Schema Mapping Providers/CustomSchemaMappingProvider.swift +++ b/Sources/Migrating/Schema Mapping Providers/CustomSchemaMappingProvider.swift @@ -35,31 +35,35 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { public enum CustomMapping: Hashable { + public typealias Transformer = (_ sourceObject: UnsafeSourceObject, _ createDestinationObject: () -> UnsafeDestinationObject) throws -> Void + case deleteEntity(sourceEntity: EntityName) case insertEntity(destinationEntity: EntityName) - case transformEntity(sourceEntity: EntityName, destinationEntity: EntityName, transformEntity: (_ sourceObject: UnsafeProxyObject, _ destinationObject: UnsafeProxyObject) throws -> Void) + case copyEntity(sourceEntity: EntityName, destinationEntity: EntityName) + case transformEntity(sourceEntity: EntityName, destinationEntity: EntityName, transformEntity: Transformer) - static func inferredTransformation(sourceObject: UnsafeProxyObject, destinationObject: UnsafeProxyObject) throws -> Void { + static func inferredTransformation(_ sourceObject: UnsafeSourceObject, _ createDestinationObject: () -> UnsafeDestinationObject) throws -> Void { - // TODO: + let destinationObject = createDestinationObject() + destinationObject.enumerateAttributes { (attribute, sourceAttribute) in + + if let sourceAttribute = sourceAttribute { + + destinationObject[attribute] = sourceObject[sourceAttribute] + } + } } -// -// case deleteAttribute(sourceEntity: EntityName, sourceAttribute: KeyPath) -// case insertAttribute(destinationEntity: EntityName, destinationAttribute: KeyPath) -// case transformAttribute(sourceEntity: EntityName, sourceAttribute: KeyPath, destinationEntity: EntityName, destinationAttribute: KeyPath, transform: (_ sourceValue: Any?) throws -> Any?) var entityMappingSourceEntity: EntityName? { switch self { case .deleteEntity(let sourceEntity), + .copyEntity(let sourceEntity, _), .transformEntity(let sourceEntity, _, _): return sourceEntity case .insertEntity: -// .insertAttribute, -// .deleteAttribute, -// .transformAttribute: return nil } } @@ -69,50 +73,15 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { switch self { case .insertEntity(let destinationEntity), + .copyEntity(_, let destinationEntity), .transformEntity(_, let destinationEntity, _): return destinationEntity case .deleteEntity: -// .deleteAttribute, -// .insertAttribute, -// .transformAttribute: return nil } } -// func attributeMappingSourceAttribute(for sourceEntity: EntityName) -> KeyPath? { -// -// switch self { -// -// case .deleteAttribute(sourceEntity, let sourceAttribute), -// .transformAttribute(sourceEntity, let sourceAttribute, _, _, _): -// return sourceAttribute -// -// case .deleteEntity, -// .insertEntity, -// .insertAttribute, -// .transformEntity: -// return nil -// } -// } -// -// func attributeMappingDestinationAttribute(for destinationEntity: EntityName) -> KeyPath? { -// -// switch self { -// -// case .insertAttribute(destinationEntity, let destinationAttribute), -// .transformAttribute(_, _, destinationEntity, let destinationAttribute, _): -// return destinationAttribute -// -// case .deleteEntity, -// .insertEntity, -// .deleteAttribute, -// .transformEntity: -// return nil -// } -// } - - // MARK: Equatable @@ -126,23 +95,13 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { case (.insertEntity(let destinationEntity1), .insertEntity(let destinationEntity2)): return destinationEntity1 == destinationEntity2 - case (.transformEntity(let sourceEntity1, let destinationEntity1, _), .transformEntity(let sourceEntity2, let destinationEntity2, _)): + case (.copyEntity(let sourceEntity1, let destinationEntity1), .copyEntity(let sourceEntity2, let destinationEntity2)): return sourceEntity1 == sourceEntity2 && destinationEntity1 == destinationEntity2 -// case (.deleteAttribute(let sourceEntity1, let sourceAttribute1), .deleteAttribute(let sourceEntity2, let sourceAttribute2)): -// return sourceEntity1 == sourceEntity2 -// && sourceAttribute1 == sourceAttribute2 -// -// case (.insertAttribute(let destinationEntity1, let destinationAttribute1), .insertAttribute(let destinationEntity2, let destinationAttribute2)): -// return destinationEntity1 == destinationEntity2 -// && destinationAttribute1 == destinationAttribute2 -// -// case (.transformAttribute(let sourceEntity1, let sourceAttribute1, let destinationEntity1, let destinationAttribute1, _), .transformAttribute(let sourceEntity2, let sourceAttribute2, let destinationEntity2, let destinationAttribute2, _)): -// return sourceEntity1 == sourceEntity2 -// && sourceAttribute1 == sourceAttribute2 -// && destinationEntity1 == destinationEntity2 -// && destinationAttribute1 == destinationAttribute2 + case (.transformEntity(let sourceEntity1, let destinationEntity1, _), .transformEntity(let sourceEntity2, let destinationEntity2, _)): + return sourceEntity1 == sourceEntity2 + && destinationEntity1 == destinationEntity2 default: return false @@ -162,53 +121,38 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { case .insertEntity(let destinationEntity): return destinationEntity.hashValue - case .transformEntity(let sourceEntity, let destinationEntity, _): + case .copyEntity(let sourceEntity, let destinationEntity): return sourceEntity.hashValue ^ destinationEntity.hashValue -// case .deleteAttribute(let sourceEntity, let sourceAttribute): -// return sourceEntity.hashValue -// ^ sourceAttribute.hashValue -// -// case .insertAttribute(let destinationEntity, let destinationAttribute): -// return destinationEntity.hashValue -// ^ destinationAttribute.hashValue -// -// case .transformAttribute(let sourceEntity, let sourceAttribute, let destinationEntity, let destinationAttribute, _): -// return sourceEntity.hashValue -// ^ sourceAttribute.hashValue -// ^ destinationEntity.hashValue -// ^ destinationAttribute.hashValue + case .transformEntity(let sourceEntity, let destinationEntity, _): + return sourceEntity.hashValue + ^ destinationEntity.hashValue } } } - // MARK: - UnsafeProxyTransaction + // MARK: - UnsafeSourceObject - public final class UnsafeProxyTransaction { + public final class UnsafeSourceObject { - func fetchAll(entityName: EntityName, _ fetchClauses: FetchClause...) -> [UnsafeProxyObject] { + public subscript(attribute: KeyPath) -> Any? { - return self.fetchAll(entityName: entityName, fetchClauses) + return self.rawObject.cs_accessValueForKVCKey(attribute) } - func fetchAll(entityName: EntityName, _ fetchClauses: [FetchClause]) -> [UnsafeProxyObject] { + public subscript(attribute: NSAttributeDescription) -> Any? { - // TODO: - fatalError() + return self.rawObject.cs_accessValueForKVCKey(attribute.name) } - } - - - // MARK: - UnsafeProxyObject - - public final class UnsafeProxyObject { - public subscript(kvcKey: KeyPath) -> Any? { + public func enumerateAttributes(_ closure: (_ attribute: NSAttributeDescription) -> Void) { - get { return self.rawObject.cs_accessValueForKVCKey(kvcKey) } - set { self.rawObject.cs_setValue(newValue, forKVCKey: kvcKey) } + for case let attribute as NSAttributeDescription in self.rawObject.entity.properties { + + closure(attribute) + } } @@ -226,6 +170,47 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { } + // MARK: - UnsafeDestinationObject + + public final class UnsafeDestinationObject { + + public subscript(attribute: KeyPath) -> Any? { + + get { return self.rawObject.cs_accessValueForKVCKey(attribute) } + set { self.rawObject.cs_setValue(newValue, forKVCKey: attribute) } + } + + public subscript(attribute: NSAttributeDescription) -> Any? { + + get { return self.rawObject.cs_accessValueForKVCKey(attribute.name) } + set { self.rawObject.cs_setValue(newValue, forKVCKey: attribute.name) } + } + + public func enumerateAttributes(_ closure: (_ attribute: NSAttributeDescription, _ sourceAttribute: NSAttributeDescription?) -> Void) { + + for case let attribute as NSAttributeDescription in self.rawObject.entity.properties { + + closure(attribute, self.sourceAttributesByDestinationKey[attribute.name]) + } + } + + + // MARK: Internal + + internal init(_ rawObject: NSManagedObject, _ sourceAttributesByDestinationKey: [KeyPath: NSAttributeDescription]) { + + self.rawObject = rawObject + self.sourceAttributesByDestinationKey = sourceAttributesByDestinationKey + } + + + // MARK: Private + + private let rawObject: NSManagedObject + private let sourceAttributesByDestinationKey: [KeyPath: NSAttributeDescription] + } + + // MARK: - Public public let sourceVersion: ModelVersion @@ -277,13 +262,13 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { let mappingModel = NSMappingModel() - let (deleteMappings, insertMappings, transformMappings) = self.resolveEntityMappings( + let (deleteMappings, insertMappings, copyMappings, transformMappings) = self.resolveEntityMappings( sourceModel: sourceModel, destinationModel: destinationModel ) func expression(forSource sourceEntity: NSEntityDescription) -> NSExpression { - return NSExpression(format: "FETCH(FUNCTION($manager, \"fetchRequestForSourceEntityNamed:predicateString:\" , \"\(sourceEntity.name!)\", \"TRUEPREDICATE\"), $manager.sourceContext, NO)") + return NSExpression(format: "FETCH(FUNCTION($\(NSMigrationManagerKey), \"fetchRequestForSourceEntityNamed:predicateString:\" , \"\(sourceEntity.name!)\", \"\(NSPredicate(value: true))\"), $\(NSMigrationManagerKey).\(#keyPath(NSMigrationManager.sourceContext)), \(false))") } let sourceEntitiesByName = sourceModel.entitiesByName @@ -317,7 +302,6 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { let propertyMapping = NSPropertyMapping() propertyMapping.name = destinationAttribute.name - propertyMapping.valueExpression = NSExpression(forConstantValue: destinationAttribute.defaultValue) attributeMappings.append(propertyMapping) } return attributeMappings @@ -335,6 +319,52 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { } entityMappings.append(entityMapping) } + for case .copyEntity(let sourceEntityName, let destinationEntityName) in copyMappings { + + let sourceEntity = sourceEntitiesByName[sourceEntityName]! + let destinationEntity = destinationEntitiesByName[destinationEntityName]! + + let entityMapping = NSEntityMapping() + entityMapping.sourceEntityName = sourceEntity.name + entityMapping.sourceEntityVersionHash = sourceEntity.versionHash + entityMapping.destinationEntityName = destinationEntity.name + entityMapping.destinationEntityVersionHash = destinationEntity.versionHash + entityMapping.mappingType = .copyEntityMappingType + entityMapping.sourceExpression = expression(forSource: sourceEntity) + entityMapping.attributeMappings = autoreleasepool { () -> [NSPropertyMapping] in + + let sourceAttributes = sourceEntity.cs_resolvedAttributeRenamingIdentities() + let destinationAttributes = destinationEntity.cs_resolvedAttributeRenamingIdentities() + + var attributeMappings: [NSPropertyMapping] = [] + for (renamingIdentifier, destination) in destinationAttributes { + + let sourceAttribute = sourceAttributes[renamingIdentifier]!.attribute + let destinationAttribute = destination.attribute + let propertyMapping = NSPropertyMapping() + propertyMapping.name = destinationAttribute.name + propertyMapping.valueExpression = NSExpression(format: "$\(NSMigrationSourceObjectKey).\(sourceAttribute.name)") + attributeMappings.append(propertyMapping) + } + return attributeMappings + } + let entityMappingName = entityMapping.name! + entityMapping.relationshipMappings = autoreleasepool { () -> [NSPropertyMapping] in + + let destinationRelationships = destinationEntity.cs_resolvedRelationshipRenamingIdentities() + var relationshipMappings: [NSPropertyMapping] = [] + for (_, destination) in destinationRelationships { + + let destinationRelationship = destination.relationship + let propertyMapping = NSPropertyMapping() + propertyMapping.name = destinationRelationship.name + propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationManagerKey), \"\(#selector(NSMigrationManager.destinationInstances(forEntityMappingName:sourceInstances:)))\" , \"\(entityMappingName)\", $\(NSMigrationSourceObjectKey))[0]") + relationshipMappings.append(propertyMapping) + } + return relationshipMappings + } + entityMappings.append(entityMapping) + } for case .transformEntity(let sourceEntityName, let destinationEntityName, let transformEntity) in transformMappings { let sourceEntity = sourceEntitiesByName[sourceEntityName]! @@ -345,153 +375,46 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { entityMapping.sourceEntityVersionHash = sourceEntity.versionHash entityMapping.destinationEntityName = destinationEntity.name entityMapping.destinationEntityVersionHash = destinationEntity.versionHash - entityMapping.mappingType = .transformEntityMappingType + entityMapping.mappingType = .customEntityMappingType entityMapping.sourceExpression = expression(forSource: sourceEntity) - entityMapping.attributeMappings = autoreleasepool { () -> [NSPropertyMapping] in + entityMapping.entityMigrationPolicyClassName = NSStringFromClass(CustomEntityMigrationPolicy.self) + + var userInfo: [AnyHashable: Any] = [ + CustomEntityMigrationPolicy.UserInfoKey.transformer: transformEntity + ] + autoreleasepool { let sourceAttributes = sourceEntity.cs_resolvedAttributeRenamingIdentities() - let destinationAttributes = sourceEntity.cs_resolvedAttributeRenamingIdentities() + let destinationAttributes = destinationEntity.cs_resolvedAttributeRenamingIdentities() - let removedAttributeKeys = Set(sourceAttributes.keys) - .subtracting(destinationAttributes.keys) - let addedAttributeKeys = Set(destinationAttributes.keys) - .subtracting(sourceAttributes.keys) - let copiedAttributeKeys = Set(destinationAttributes.keys) - .subtracting(addedAttributeKeys) - .subtracting(removedAttributeKeys) - .filter({ sourceAttributes[$0]!.versionHash == destinationAttributes[$0]!.versionHash }) - let transformedAttributeKeys = Set(destinationAttributes.keys) - .subtracting(addedAttributeKeys) - .subtracting(removedAttributeKeys) - .subtracting(copiedAttributeKeys) - - var attributeMappings: [NSPropertyMapping] = [] -// for attributeKey in removedAttributeKeys { -// -// let propertyMapping = NSPropertyMapping() -// propertyMapping.name = sourceAttributes[attributeKey]!.attribute.name -// attributeMappings.append(propertyMapping) -// } - for attributeKey in transformedAttributeKeys { + let transformedRenamingIdentifiers = Set(destinationAttributes.keys) + .intersection(sourceAttributes.keys) + + var sourceAttributesByDestinationKey: [KeyPath: NSAttributeDescription] = [:] + for renamingIdentifier in transformedRenamingIdentifiers { - let sourceAttribute = sourceAttributes[attributeKey]!.attribute - let destinationAttribute = destinationAttributes[attributeKey]!.attribute - // TODO: assert valid and invalid transformations - let propertyMapping = NSPropertyMapping() - propertyMapping.name = destinationAttribute.name - propertyMapping.valueExpression = NSExpression(format: "$source.\(sourceAttribute.name)") - attributeMappings.append(propertyMapping) + let sourceAttribute = sourceAttributes[renamingIdentifier]!.attribute + let destinationAttribute = destinationAttributes[renamingIdentifier]!.attribute + sourceAttributesByDestinationKey[destinationAttribute.name] = sourceAttribute } - for attributeKey in copiedAttributeKeys { - - let sourceAttribute = sourceAttributes[attributeKey]!.attribute - let destinationAttribute = destinationAttributes[attributeKey]!.attribute - let propertyMapping = NSPropertyMapping() - propertyMapping.name = destinationAttribute.name - propertyMapping.valueExpression = NSExpression(format: "$source.\(sourceAttribute.name)") - attributeMappings.append(propertyMapping) - } - for attributeKey in addedAttributeKeys { - - let destinationAttribute = destinationAttributes[attributeKey]!.attribute - let propertyMapping = NSPropertyMapping() - propertyMapping.name = destinationAttribute.name - propertyMapping.valueExpression = NSExpression(forConstantValue: destinationAttribute.defaultValue) - attributeMappings.append(propertyMapping) - } - return attributeMappings + userInfo[CustomEntityMigrationPolicy.UserInfoKey.sourceAttributesByDestinationKey] = sourceAttributesByDestinationKey } + let entityMappingName = entityMapping.name! entityMapping.relationshipMappings = autoreleasepool { () -> [NSPropertyMapping] in - let sourceRelationships = source.entity.cs_resolvedRelationshipRenamingIdentities() - let destinationRelationships = destination.entity.cs_resolvedRelationshipRenamingIdentities() - - let removedRelationshipKeys = Set(sourceRelationships.keys) - .subtracting(destinationRelationships.keys) - let addedRelationshipKeys = Set(destinationRelationships.keys) - .subtracting(sourceRelationships.keys) - let copiedRelationshipKeys = Set(destinationRelationships.keys) - .subtracting(addedRelationshipKeys) - .subtracting(removedRelationshipKeys) - .filter({ sourceRelationships[$0]!.versionHash == destinationRelationships[$0]!.versionHash }) - let transformedRelationshipKeys = Set(destinationRelationships.keys) - .subtracting(addedRelationshipKeys) - .subtracting(removedRelationshipKeys) - .subtracting(copiedRelationshipKeys) - + let destinationRelationships = destinationEntity.cs_resolvedRelationshipRenamingIdentities() var relationshipMappings: [NSPropertyMapping] = [] - for relationshipKey in removedRelationshipKeys { + for (_, destination) in destinationRelationships { - let propertyMapping = NSPropertyMapping() - propertyMapping.name = sourceRelationships[relationshipKey]!.relationship.name - relationshipMappings.append(propertyMapping) - } - for attributeKey in transformedRelationshipKeys { - - let sourceRelationship = sourceRelationships[attributeKey]!.relationship - let destinationRelationship = destinationRelationships[attributeKey]!.relationship - // TODO: assert valid and invalid transformations - let propertyMapping = NSPropertyMapping() - propertyMapping.name = destinationRelationship.name - propertyMapping.valueExpression = NSExpression(format: "$source.\(sourceRelationship.name)") - relationshipMappings.append(propertyMapping) - } - for attributeKey in copiedRelationshipKeys { - - let sourceRelationship = sourceRelationships[attributeKey]!.relationship - let destinationRelationship = destinationRelationships[attributeKey]!.relationship - let propertyMapping = NSPropertyMapping() - propertyMapping.name = destinationRelationship.name - propertyMapping.valueExpression = NSExpression(format: "$source.\(sourceRelationship.name)") - relationshipMappings.append(propertyMapping) - } - for attributeKey in addedRelationshipKeys { - - let destinationRelationship = destinationRelationships[attributeKey]!.relationship + let destinationRelationship = destination.relationship let propertyMapping = NSPropertyMapping() propertyMapping.name = destinationRelationship.name + propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationManagerKey), \"\(#selector(NSMigrationManager.destinationInstances(forEntityMappingName:sourceInstances:)))\" , \"\(entityMappingName)\", $\(NSMigrationSourceObjectKey))[0]") relationshipMappings.append(propertyMapping) } return relationshipMappings } - entityMappings.append(entityMapping) - } - for entityKey in copiedEntityKeys { - - let source = sourceEntities[entityKey]! - let destination = destinationEntities[entityKey]! - - let entityMapping = NSEntityMapping() - entityMapping.sourceEntityName = source.entity.name - entityMapping.sourceEntityVersionHash = source.versionHash - entityMapping.destinationEntityName = destination.entity.name - entityMapping.destinationEntityVersionHash = destination.versionHash - entityMapping.mappingType = .copyEntityMappingType - entityMapping.sourceExpression = expression(forSource: source.entity) - entityMapping.attributeMappings = autoreleasepool { () -> [NSPropertyMapping] in - - var attributeMappings: [NSPropertyMapping] = [] - for (_, sourceAttribute) in source.entity.attributesByName { - - let propertyMapping = NSPropertyMapping() - propertyMapping.name = sourceAttribute.name - propertyMapping.valueExpression = NSExpression(format: "$source.\(sourceAttribute.name)") - attributeMappings.append(propertyMapping) - } - return attributeMappings - } - entityMapping.relationshipMappings = autoreleasepool { () -> [NSPropertyMapping] in - - var relationshipMappings: [NSPropertyMapping] = [] - for (_, sourceRelationship) in source.entity.relationshipsByName { - - let propertyMapping = NSPropertyMapping() - propertyMapping.name = sourceRelationship.name - propertyMapping.valueExpression = NSExpression(format: "$source.\(sourceRelationship.name)") - relationshipMappings.append(propertyMapping) - } - return relationshipMappings - } + entityMapping.userInfo = userInfo entityMappings.append(entityMapping) } @@ -512,15 +435,49 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { private final class CustomEntityMigrationPolicy: NSEntityMigrationPolicy { + // MARK: NSEntityMigrationPolicy + override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws { - try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager) + let userInfo = mapping.userInfo! + let transformer = userInfo[CustomEntityMigrationPolicy.UserInfoKey.transformer]! as! CustomMapping.Transformer + let sourceAttributesByDestinationKey = userInfo[CustomEntityMigrationPolicy.UserInfoKey.sourceAttributesByDestinationKey] as! [KeyPath: NSAttributeDescription] + + var dInstance: NSManagedObject? + try transformer( + UnsafeSourceObject(sInstance), + { + let rawObject = NSEntityDescription.insertNewObject( + forEntityName: mapping.destinationEntityName!, + into: manager.destinationContext + ) + dInstance = rawObject + return UnsafeDestinationObject(rawObject, sourceAttributesByDestinationKey) + } + ) + if let dInstance = dInstance { + + manager.associate( + sourceInstance: sInstance, + withDestinationInstance: dInstance, + for: mapping + ) + } } override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws { try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager) } + + + // MARK: FilePrivate + + fileprivate enum UserInfoKey { + + fileprivate static let transformer = "CoreStore.CustomEntityMigrationPolicy.transformer" + fileprivate static let sourceAttributesByDestinationKey = "CoreStore.CustomEntityMigrationPolicy.sourceAttributesByDestinationKey" + } } @@ -528,10 +485,11 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { private let entityMappings: Set - private func resolveEntityMappings(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel) -> (delete: Set, insert: Set, transform: Set) { + private func resolveEntityMappings(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel) -> (delete: Set, insert: Set, copy: Set, transform: Set) { var deleteMappings: Set = [] var insertMappings: Set = [] + var copyMappings: Set = [] var transformMappings: Set = [] var allMappedSourceKeys: [KeyPath: KeyPath] = [:] var allMappedDestinationKeys: [KeyPath: KeyPath] = [:] @@ -598,6 +556,31 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { transformMappings.insert(mapping) allMappedSourceKeys[sourceEntity] = destinationEntity allMappedDestinationKeys[destinationEntity] = sourceEntity + + case .copyEntity(let sourceEntity, let destinationEntity): + CoreStore.assert( + sourceEntityNames[sourceEntity] != nil, + "A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' passed to \(cs_typeName(CustomSchemaMappingProvider.self)) could not be mapped to any \(cs_typeName(NSEntityDescription.self)) from the source \(cs_typeName(NSManagedObjectModel.self))." + ) + CoreStore.assert( + destinationEntityNames[destinationEntity] != nil, + "A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' passed to \(cs_typeName(CustomSchemaMappingProvider.self)) could not be mapped to any \(cs_typeName(NSEntityDescription.self)) from the destination \(cs_typeName(NSManagedObjectModel.self))." + ) + CoreStore.assert( + sourceEntityNames[sourceEntity]!.versionHash == destinationEntityNames[destinationEntity]!.versionHash, + "A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' was passed to \(cs_typeName(CustomSchemaMappingProvider.self)) but the \(cs_typeName(NSEntityDescription.self))'s \"versionHash\" of the source and destination entities do not match." + ) + CoreStore.assert( + allMappedSourceKeys[sourceEntity] == nil, + "Duplicate \(cs_typeName(CustomMapping.self))s found for source entity name \"\(sourceEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))." + ) + CoreStore.assert( + allMappedDestinationKeys[destinationEntity] == nil, + "Duplicate \(cs_typeName(CustomMapping.self))s found for destination entity name \"\(destinationEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))." + ) + copyMappings.insert(mapping) + allMappedSourceKeys[sourceEntity] = destinationEntity + allMappedDestinationKeys[destinationEntity] = sourceEntity } for renamingIdentifier in transformedRenamingIdentifiers { @@ -609,13 +592,25 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { switch (allMappedSourceKeys[sourceEntityName], allMappedDestinationKeys[destinationEntityName]) { case (nil, nil): - transformMappings.insert( - .transformEntity( - sourceEntity: sourceEntityName, - destinationEntity: destinationEntityName, - transformEntity: CustomMapping.inferredTransformation + if sourceEntity.versionHash == destinationEntity.versionHash { + + copyMappings.insert( + .copyEntity( + sourceEntity: sourceEntityName, + destinationEntity: destinationEntityName + ) ) - ) + } + else { + + transformMappings.insert( + .transformEntity( + sourceEntity: sourceEntityName, + destinationEntity: destinationEntityName, + transformEntity: CustomMapping.inferredTransformation + ) + ) + } allMappedSourceKeys[sourceEntityName] = destinationEntityName allMappedDestinationKeys[destinationEntityName] = sourceEntityName @@ -660,6 +655,6 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider { } } } - return (deleteMappings, insertMappings, transformMappings) + return (deleteMappings, insertMappings, copyMappings, transformMappings) } }