mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-31 22:43:13 +02:00
WIP: custom migration
This commit is contained in:
@@ -0,0 +1,665 @@
|
||||
//
|
||||
// CustomSchemaMappingProvider.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - CustomSchemaMappingProvider
|
||||
|
||||
open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
||||
|
||||
// MARK: - CustomMapping
|
||||
|
||||
public enum CustomMapping: Hashable {
|
||||
|
||||
case deleteEntity(sourceEntity: EntityName)
|
||||
case insertEntity(destinationEntity: EntityName)
|
||||
case transformEntity(sourceEntity: EntityName, destinationEntity: EntityName, transformEntity: (_ sourceObject: UnsafeProxyObject, _ destinationObject: UnsafeProxyObject) throws -> Void)
|
||||
|
||||
static func inferredTransformation(sourceObject: UnsafeProxyObject, destinationObject: UnsafeProxyObject) throws -> Void {
|
||||
|
||||
// TODO:
|
||||
}
|
||||
//
|
||||
// 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),
|
||||
.transformEntity(let sourceEntity, _, _):
|
||||
return sourceEntity
|
||||
|
||||
case .insertEntity:
|
||||
// .insertAttribute,
|
||||
// .deleteAttribute,
|
||||
// .transformAttribute:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var entityMappingDestinationEntity: EntityName? {
|
||||
|
||||
switch self {
|
||||
|
||||
case .insertEntity(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
|
||||
|
||||
public static func == (lhs: CustomMapping, rhs: CustomMapping) -> Bool {
|
||||
|
||||
switch (lhs, rhs) {
|
||||
|
||||
case (.deleteEntity(let sourceEntity1), .deleteEntity(let sourceEntity2)):
|
||||
return sourceEntity1 == sourceEntity2
|
||||
|
||||
case (.insertEntity(let destinationEntity1), .insertEntity(let destinationEntity2)):
|
||||
return destinationEntity1 == destinationEntity2
|
||||
|
||||
case (.transformEntity(let sourceEntity1, let destinationEntity1, _), .transformEntity(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
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
switch self {
|
||||
|
||||
case .deleteEntity(let sourceEntity):
|
||||
return sourceEntity.hashValue
|
||||
|
||||
case .insertEntity(let destinationEntity):
|
||||
return destinationEntity.hashValue
|
||||
|
||||
case .transformEntity(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UnsafeProxyTransaction
|
||||
|
||||
public final class UnsafeProxyTransaction {
|
||||
|
||||
func fetchAll(entityName: EntityName, _ fetchClauses: FetchClause...) -> [UnsafeProxyObject] {
|
||||
|
||||
return self.fetchAll(entityName: entityName, fetchClauses)
|
||||
}
|
||||
|
||||
func fetchAll(entityName: EntityName, _ fetchClauses: [FetchClause]) -> [UnsafeProxyObject] {
|
||||
|
||||
// TODO:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UnsafeProxyObject
|
||||
|
||||
public final class UnsafeProxyObject {
|
||||
|
||||
public subscript(kvcKey: KeyPath) -> Any? {
|
||||
|
||||
get { return self.rawObject.cs_accessValueForKVCKey(kvcKey) }
|
||||
set { self.rawObject.cs_setValue(newValue, forKVCKey: kvcKey) }
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(_ rawObject: NSManagedObject) {
|
||||
|
||||
self.rawObject = rawObject
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let rawObject: NSManagedObject
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
public let sourceVersion: ModelVersion
|
||||
public let destinationVersion: ModelVersion
|
||||
|
||||
public required init(from sourceVersion: ModelVersion, to destinationVersion: ModelVersion, entityMappings: Set<CustomMapping> = []) {
|
||||
|
||||
CoreStore.assert(
|
||||
cs_lazy {
|
||||
|
||||
let sources = entityMappings.flatMap({ $0.entityMappingSourceEntity })
|
||||
let destinations = entityMappings.flatMap({ $0.entityMappingDestinationEntity })
|
||||
return sources.count == Set(sources).count
|
||||
&& destinations.count == Set(destinations).count
|
||||
},
|
||||
"Duplicate source/destination entities found in provided \"entityMappings\" argument."
|
||||
)
|
||||
self.sourceVersion = sourceVersion
|
||||
self.destinationVersion = destinationVersion
|
||||
self.entityMappings = entityMappings
|
||||
}
|
||||
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
public static func == (lhs: CustomSchemaMappingProvider, rhs: CustomSchemaMappingProvider) -> Bool {
|
||||
|
||||
return lhs.sourceVersion == rhs.sourceVersion
|
||||
&& lhs.destinationVersion == rhs.destinationVersion
|
||||
&& type(of: lhs) == type(of: rhs)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
return self.sourceVersion.hashValue
|
||||
^ self.destinationVersion.hashValue
|
||||
}
|
||||
|
||||
|
||||
// MARK: SchemaMappingProvider
|
||||
|
||||
public func createMappingModel(from sourceSchema: DynamicSchema, to destinationSchema: DynamicSchema, storage: LocalStorage) throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) {
|
||||
|
||||
let sourceModel = sourceSchema.rawModel()
|
||||
let destinationModel = destinationSchema.rawModel()
|
||||
|
||||
let mappingModel = NSMappingModel()
|
||||
|
||||
let (deleteMappings, insertMappings, 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)")
|
||||
}
|
||||
|
||||
let sourceEntitiesByName = sourceModel.entitiesByName
|
||||
let destinationEntitiesByName = destinationModel.entitiesByName
|
||||
|
||||
var entityMappings: [NSEntityMapping] = []
|
||||
for case .deleteEntity(let sourceEntityName) in deleteMappings {
|
||||
|
||||
let sourceEntity = sourceEntitiesByName[sourceEntityName]!
|
||||
|
||||
let entityMapping = NSEntityMapping()
|
||||
entityMapping.sourceEntityName = sourceEntity.name
|
||||
entityMapping.sourceEntityVersionHash = sourceEntity.versionHash
|
||||
entityMapping.mappingType = .removeEntityMappingType
|
||||
entityMapping.sourceExpression = expression(forSource: sourceEntity)
|
||||
|
||||
entityMappings.append(entityMapping)
|
||||
}
|
||||
for case .insertEntity(let destinationEntityName) in insertMappings {
|
||||
|
||||
let destinationEntity = destinationEntitiesByName[destinationEntityName]!
|
||||
|
||||
let entityMapping = NSEntityMapping()
|
||||
entityMapping.destinationEntityName = destinationEntity.name
|
||||
entityMapping.destinationEntityVersionHash = destinationEntity.versionHash
|
||||
entityMapping.mappingType = .addEntityMappingType
|
||||
entityMapping.attributeMappings = autoreleasepool { () -> [NSPropertyMapping] in
|
||||
|
||||
var attributeMappings: [NSPropertyMapping] = []
|
||||
for (_, destinationAttribute) in destinationEntity.attributesByName {
|
||||
|
||||
let propertyMapping = NSPropertyMapping()
|
||||
propertyMapping.name = destinationAttribute.name
|
||||
propertyMapping.valueExpression = NSExpression(forConstantValue: destinationAttribute.defaultValue)
|
||||
attributeMappings.append(propertyMapping)
|
||||
}
|
||||
return attributeMappings
|
||||
}
|
||||
entityMapping.relationshipMappings = autoreleasepool { () -> [NSPropertyMapping] in
|
||||
|
||||
var relationshipMappings: [NSPropertyMapping] = []
|
||||
for (_, destinationRelationship) in destinationEntity.relationshipsByName {
|
||||
|
||||
let propertyMapping = NSPropertyMapping()
|
||||
propertyMapping.name = destinationRelationship.name
|
||||
relationshipMappings.append(propertyMapping)
|
||||
}
|
||||
return relationshipMappings
|
||||
}
|
||||
entityMappings.append(entityMapping)
|
||||
}
|
||||
for case .transformEntity(let sourceEntityName, let destinationEntityName, let transformEntity) in transformMappings {
|
||||
|
||||
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 = .transformEntityMappingType
|
||||
entityMapping.sourceExpression = expression(forSource: sourceEntity)
|
||||
entityMapping.attributeMappings = autoreleasepool { () -> [NSPropertyMapping] in
|
||||
|
||||
let sourceAttributes = sourceEntity.cs_resolvedAttributeRenamingIdentities()
|
||||
let destinationAttributes = sourceEntity.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 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)
|
||||
}
|
||||
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
|
||||
}
|
||||
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)
|
||||
|
||||
var relationshipMappings: [NSPropertyMapping] = []
|
||||
for relationshipKey in removedRelationshipKeys {
|
||||
|
||||
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 propertyMapping = NSPropertyMapping()
|
||||
propertyMapping.name = destinationRelationship.name
|
||||
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
|
||||
}
|
||||
entityMappings.append(entityMapping)
|
||||
}
|
||||
|
||||
mappingModel.entityMappings = entityMappings
|
||||
return (
|
||||
mappingModel,
|
||||
.heavyweight(
|
||||
sourceVersion: self.sourceVersion,
|
||||
destinationVersion: self.destinationVersion
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: - CustomEntityMigrationPolicy
|
||||
|
||||
private final class CustomEntityMigrationPolicy: NSEntityMigrationPolicy {
|
||||
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
}
|
||||
|
||||
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
|
||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
|
||||
private let entityMappings: Set<CustomMapping>
|
||||
|
||||
private func resolveEntityMappings(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel) -> (delete: Set<CustomMapping>, insert: Set<CustomMapping>, transform: Set<CustomMapping>) {
|
||||
|
||||
var deleteMappings: Set<CustomMapping> = []
|
||||
var insertMappings: Set<CustomMapping> = []
|
||||
var transformMappings: Set<CustomMapping> = []
|
||||
var allMappedSourceKeys: [KeyPath: KeyPath] = [:]
|
||||
var allMappedDestinationKeys: [KeyPath: KeyPath] = [:]
|
||||
|
||||
let sourceRenamingIdentifiers = sourceModel.cs_resolvedRenamingIdentities()
|
||||
let sourceEntityNames = sourceModel.entitiesByName
|
||||
let destinationRenamingIdentifiers = destinationModel.cs_resolvedRenamingIdentities()
|
||||
let destinationEntityNames = destinationModel.entitiesByName
|
||||
|
||||
let removedRenamingIdentifiers = Set(sourceRenamingIdentifiers.keys)
|
||||
.subtracting(destinationRenamingIdentifiers.keys)
|
||||
let addedRenamingIdentifiers = Set(destinationRenamingIdentifiers.keys)
|
||||
.subtracting(sourceRenamingIdentifiers.keys)
|
||||
let transformedRenamingIdentifiers = Set(destinationRenamingIdentifiers.keys)
|
||||
.subtracting(addedRenamingIdentifiers)
|
||||
.subtracting(removedRenamingIdentifiers)
|
||||
|
||||
// First pass: resolve source-destination entities
|
||||
for mapping in self.entityMappings {
|
||||
|
||||
switch mapping {
|
||||
|
||||
case .deleteEntity(let sourceEntity):
|
||||
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(
|
||||
allMappedSourceKeys[sourceEntity] == nil,
|
||||
"Duplicate \(cs_typeName(CustomMapping.self))s found for source entity name \"\(sourceEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))."
|
||||
)
|
||||
deleteMappings.insert(mapping)
|
||||
allMappedSourceKeys[sourceEntity] = ""
|
||||
|
||||
case .insertEntity(let destinationEntity):
|
||||
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(
|
||||
allMappedDestinationKeys[destinationEntity] == nil,
|
||||
"Duplicate \(cs_typeName(CustomMapping.self))s found for destination entity name \"\(destinationEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))."
|
||||
)
|
||||
insertMappings.insert(mapping)
|
||||
allMappedDestinationKeys[destinationEntity] = ""
|
||||
|
||||
case .transformEntity(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(
|
||||
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))."
|
||||
)
|
||||
transformMappings.insert(mapping)
|
||||
allMappedSourceKeys[sourceEntity] = destinationEntity
|
||||
allMappedDestinationKeys[destinationEntity] = sourceEntity
|
||||
}
|
||||
|
||||
for renamingIdentifier in transformedRenamingIdentifiers {
|
||||
|
||||
let sourceEntity = sourceRenamingIdentifiers[renamingIdentifier]!.entity
|
||||
let destinationEntity = destinationRenamingIdentifiers[renamingIdentifier]!.entity
|
||||
let sourceEntityName = sourceEntity.name!
|
||||
let destinationEntityName = destinationEntity.name!
|
||||
switch (allMappedSourceKeys[sourceEntityName], allMappedDestinationKeys[destinationEntityName]) {
|
||||
|
||||
case (nil, nil):
|
||||
transformMappings.insert(
|
||||
.transformEntity(
|
||||
sourceEntity: sourceEntityName,
|
||||
destinationEntity: destinationEntityName,
|
||||
transformEntity: CustomMapping.inferredTransformation
|
||||
)
|
||||
)
|
||||
allMappedSourceKeys[sourceEntityName] = destinationEntityName
|
||||
allMappedDestinationKeys[destinationEntityName] = sourceEntityName
|
||||
|
||||
case (""?, nil):
|
||||
insertMappings.insert(.insertEntity(destinationEntity: destinationEntityName))
|
||||
allMappedDestinationKeys[destinationEntityName] = ""
|
||||
|
||||
case (nil, ""?):
|
||||
deleteMappings.insert(.deleteEntity(sourceEntity: sourceEntityName))
|
||||
allMappedSourceKeys[sourceEntityName] = ""
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
for renamingIdentifier in removedRenamingIdentifiers {
|
||||
|
||||
let sourceEntity = sourceRenamingIdentifiers[renamingIdentifier]!.entity
|
||||
let sourceEntityName = sourceEntity.name!
|
||||
switch allMappedSourceKeys[sourceEntityName] {
|
||||
|
||||
case nil:
|
||||
deleteMappings.insert(.deleteEntity(sourceEntity: sourceEntityName))
|
||||
allMappedSourceKeys[sourceEntityName] = ""
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
for renamingIdentifier in addedRenamingIdentifiers {
|
||||
|
||||
let destinationEntity = destinationRenamingIdentifiers[renamingIdentifier]!.entity
|
||||
let destinationEntityName = destinationEntity.name!
|
||||
switch allMappedDestinationKeys[destinationEntityName] {
|
||||
|
||||
case nil:
|
||||
insertMappings.insert(.insertEntity(destinationEntity: destinationEntityName))
|
||||
allMappedDestinationKeys[destinationEntityName] = ""
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return (deleteMappings, insertMappings, transformMappings)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// InferredSchemaMappingProvider.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - InferredSchemaMappingProvider
|
||||
|
||||
final class InferredSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
public static func == (lhs: InferredSchemaMappingProvider, rhs: InferredSchemaMappingProvider) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
return ObjectIdentifier(type(of: self)).hashValue
|
||||
}
|
||||
|
||||
|
||||
// MARK: SchemaMappingProvider
|
||||
|
||||
public func createMappingModel(from sourceSchema: DynamicSchema, to destinationSchema: DynamicSchema, storage: LocalStorage) throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) {
|
||||
|
||||
let sourceModel = sourceSchema.rawModel()
|
||||
let destinationModel = destinationSchema.rawModel()
|
||||
|
||||
if let mappingModel = NSMappingModel(
|
||||
from: Bundle.allBundles,
|
||||
forSourceModel: sourceModel,
|
||||
destinationModel: destinationModel) {
|
||||
|
||||
return (
|
||||
mappingModel,
|
||||
.heavyweight(
|
||||
sourceVersion: sourceSchema.modelVersion,
|
||||
destinationVersion: destinationSchema.modelVersion
|
||||
)
|
||||
)
|
||||
}
|
||||
do {
|
||||
|
||||
let mappingModel = try NSMappingModel.inferredMappingModel(
|
||||
forSourceModel: sourceModel,
|
||||
destinationModel: destinationModel
|
||||
)
|
||||
return (
|
||||
mappingModel,
|
||||
.lightweight(
|
||||
sourceVersion: sourceSchema.modelVersion,
|
||||
destinationVersion: destinationSchema.modelVersion
|
||||
)
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
throw CoreStoreError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// SchemaMappingProvider.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - SchemaMappingProvider
|
||||
|
||||
public protocol SchemaMappingProvider {
|
||||
|
||||
func createMappingModel(from sourceSchema: DynamicSchema, to destinationSchema: DynamicSchema, storage: LocalStorage) throws -> (mappingModel: NSMappingModel, migrationType: MigrationType)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// XcodeSchemaMappingProvider.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - XcodeSchemaMappingProvider
|
||||
|
||||
final class XcodeSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
||||
|
||||
public let sourceVersion: ModelVersion
|
||||
public let destinationVersion: ModelVersion
|
||||
public let mappingModelBundle: Bundle
|
||||
|
||||
public required init(from sourceVersion: ModelVersion, to destinationVersion: ModelVersion, mappingModelBundle: Bundle) {
|
||||
|
||||
self.sourceVersion = sourceVersion
|
||||
self.destinationVersion = destinationVersion
|
||||
self.mappingModelBundle = mappingModelBundle
|
||||
}
|
||||
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
public static func == (lhs: XcodeSchemaMappingProvider, rhs: XcodeSchemaMappingProvider) -> Bool {
|
||||
|
||||
return lhs.sourceVersion == rhs.sourceVersion
|
||||
&& lhs.destinationVersion == rhs.destinationVersion
|
||||
&& lhs.mappingModelBundle == rhs.mappingModelBundle
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
return self.sourceVersion.hashValue
|
||||
^ self.destinationVersion.hashValue
|
||||
}
|
||||
|
||||
|
||||
// MARK: SchemaMappingProvider
|
||||
|
||||
public func createMappingModel(from sourceSchema: DynamicSchema, to destinationSchema: DynamicSchema, storage: LocalStorage) throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) {
|
||||
|
||||
let sourceModel = sourceSchema.rawModel()
|
||||
let destinationModel = destinationSchema.rawModel()
|
||||
|
||||
if let mappingModel = NSMappingModel(
|
||||
from: [self.mappingModelBundle],
|
||||
forSourceModel: sourceModel,
|
||||
destinationModel: destinationModel) {
|
||||
|
||||
return (
|
||||
mappingModel,
|
||||
.heavyweight(
|
||||
sourceVersion: sourceSchema.modelVersion,
|
||||
destinationVersion: destinationSchema.modelVersion
|
||||
)
|
||||
)
|
||||
}
|
||||
throw CoreStoreError.mappingModelNotFound(
|
||||
localStoreURL: storage.fileURL,
|
||||
targetModel: destinationModel,
|
||||
targetModelVersion: destinationSchema.modelVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user