WIP: custom migration

This commit is contained in:
John Rommel Estropia
2017-05-09 03:10:35 +09:00
parent 6d04806608
commit 9d65a27557
37 changed files with 1642 additions and 588 deletions

View File

@@ -74,7 +74,7 @@ internal struct EntityIdentifier: Hashable {
internal init(_ entityDescription: NSEntityDescription) {
if let anyEntity = entityDescription.anyEntity {
if let anyEntity = entityDescription.coreStoreEntity {
self.category = .coreStore
self.interfacedClassName = NSStringFromClass(anyEntity.type)

View File

@@ -32,7 +32,7 @@ import Foundation
internal extension NSEntityDescription {
@nonobjc
internal var anyEntity: CoreStoreSchema.AnyEntity? {
internal var coreStoreEntity: CoreStoreSchema.AnyEntity? {
get {

View File

@@ -0,0 +1,55 @@
//
// NSEntityDescription+Migration.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: - NSEntityDescription
internal extension NSEntityDescription {
@nonobjc
internal func cs_resolvedAttributeRenamingIdentities() -> [String: (attribute: NSAttributeDescription, versionHash: Data)] {
var mapping: [String: (attribute: NSAttributeDescription, versionHash: Data)] = [:]
for (attributeName, attributeDescription) in self.attributesByName {
mapping[attributeDescription.renamingIdentifier ?? attributeName] = (attributeDescription, attributeDescription.versionHash)
}
return mapping
}
@nonobjc
internal func cs_resolvedRelationshipRenamingIdentities() -> [String: (relationship: NSRelationshipDescription, versionHash: Data)] {
var mapping: [String: (relationship: NSRelationshipDescription, versionHash: Data)] = [:]
for (relationshipName, relationshipDescription) in self.relationshipsByName {
mapping[relationshipDescription.renamingIdentifier ?? relationshipName] = (relationshipDescription, relationshipDescription.versionHash)
}
return mapping
}
}

View File

@@ -0,0 +1,44 @@
//
// NSManagedObjectModel+Migration.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: - FilePrivate
internal extension NSManagedObjectModel {
@nonobjc
internal func cs_resolvedRenamingIdentities() -> [String: (entity: NSEntityDescription, versionHash: Data)] {
var mapping: [String: (entity: NSEntityDescription, versionHash: Data)] = [:]
for (entityName, entityDescription) in self.entitiesByName {
mapping[entityDescription.renamingIdentifier ?? entityName] = (entityDescription, entityDescription.versionHash)
}
return mapping
}
}

View File

@@ -372,34 +372,6 @@ extension Into: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
}
// MARK: - LegacySQLiteStore
extension LegacySQLiteStore: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
return createFormattedString(
"(", ")",
("configuration", self.configuration as Any),
("storeOptions", self.storeOptions as Any),
("fileURL", self.fileURL),
("mappingModelBundles", self.mappingModelBundles),
("localStorageOptions", self.localStorageOptions)
)
}
}
// MARK: - LegacyXcodeDataModelSchema
extension LegacyXcodeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
@@ -893,7 +865,7 @@ extension SQLiteStore: CustomDebugStringConvertible, CoreStoreDebugStringConvert
("configuration", self.configuration as Any),
("storeOptions", self.storeOptions as Any),
("fileURL", self.fileURL),
("mappingModelBundles", self.mappingModelBundles),
("migrationMappingProviders", self.migrationMappingProviders),
("localStorageOptions", self.localStorageOptions)
)
}

View File

@@ -63,7 +63,7 @@ public extension CoreStore {
}
@inline(__always)
internal static func assert( _ condition: @autoclosure () -> Bool, _ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
internal static func assert( _ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
self.logger.assert(
condition,

View File

@@ -30,27 +30,6 @@ import CoreData
// MARK: - CoreStore
public extension CoreStore {
/**
Asynchronously adds a `StorageInterface` with default settings to the `defaultStack`. Migrations are also initiated by default.
```
CoreStore.addStorage(
InMemoryStore.self,
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storeType: the storage type
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `StorageInterface` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
*/
public static func addStorage<T: StorageInterface>(_ storeType: T.Type, completion: @escaping (SetupResult<T>) -> Void) where T: DefaultInitializableStore {
self.defaultStack.addStorage(storeType.init(), completion: completion)
}
/**
Asynchronously adds a `StorageInterface` to the `defaultStack`. Migrations are also initiated by default.
@@ -72,28 +51,6 @@ public extension CoreStore {
self.defaultStack.addStorage(storage, completion: completion)
}
/**
Asynchronously adds a `LocalStorage` with default settings to the `defaultStack`. Migrations are also initiated by default.
```
let migrationProgress = CoreStore.addStorage(
SQLiteStore.self,
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storeType: the local storage type
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `LocalStorage` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
- returns: a `Progress` instance if a migration has started, or `nil` if either no migrations are required or if a failure occured.
*/
public static func addStorage<T: LocalStorage>(_ storeType: T.Type, completion: @escaping (SetupResult<T>) -> Void) -> Progress? where T: DefaultInitializableStore {
return self.defaultStack.addStorage(storeType.init(), completion: completion)
}
/**
Asynchronously adds a `LocalStorage` to the `defaultStack`. Migrations are also initiated by default.

View File

@@ -31,27 +31,6 @@ import CoreData
public extension DataStack {
/**
Asynchronously adds a `StorageInterface` with default settings to the stack. Migrations are also initiated by default.
```
dataStack.addStorage(
InMemoryStore.self,
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storeType: the storage type
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `StorageInterface` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
*/
public func addStorage<T: StorageInterface>(_ storeType: T.Type, completion: @escaping (SetupResult<T>) -> Void) where T: DefaultInitializableStore {
self.addStorage(storeType.init(), completion: completion)
}
/**
Asynchronously adds a `StorageInterface` to the stack. Migrations are also initiated by default.
```
@@ -109,28 +88,6 @@ public extension DataStack {
}
}
/**
Asynchronously adds a `LocalStorage` with default settings to the stack. Migrations are also initiated by default.
```
let migrationProgress = dataStack.addStorage(
SQLiteStore.self,
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storeType: the local storage type
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `LocalStorage` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
- returns: a `Progress` instance if a migration has started, or `nil` if either no migrations are required or if a failure occured.
*/
public func addStorage<T: LocalStorage>(_ storeType: T.Type, completion: @escaping (SetupResult<T>) -> Void) -> Progress? where T: DefaultInitializableStore {
return self.addStorage(storeType.init() as! T.Type, completion: completion)
}
/**
Asynchronously adds a `LocalStorage` to the stack. Migrations are also initiated by default.
```
@@ -683,52 +640,33 @@ public extension DataStack {
var migrationSteps = [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]()
while let nextVersion = migrationChain.nextVersionFrom(currentVersion),
let sourceModel = schemaHistory.rawModel(for: currentVersion),
sourceModel != schemaHistory.rawModel,
let destinationModel = schemaHistory.rawModel(for: nextVersion) {
let sourceSchema = schemaHistory.schema(for: currentVersion),
sourceSchema.modelVersion != schemaHistory.currentModelVersion,
let destinationSchema = schemaHistory.schema(for: nextVersion) {
if let mappingModel = NSMappingModel(
from: storage.mappingModelBundles,
forSourceModel: sourceModel,
destinationModel: destinationModel) {
let mappingProviders = storage.migrationMappingProviders
do {
migrationSteps.append(
(
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
migrationType: .heavyweight(
sourceVersion: currentVersion,
destinationVersion: nextVersion
)
)
)
}
else {
do {
try withExtendedLifetime((sourceSchema.rawModel(), destinationSchema.rawModel())) { (sourceModel, destinationModel) in
let mappingModel = try NSMappingModel.inferredMappingModel(
forSourceModel: sourceModel,
destinationModel: destinationModel
let mapping = try mappingProviders.findMapping(
sourceSchema: sourceSchema,
destinationSchema: destinationSchema,
storage: storage
)
migrationSteps.append(
(
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
migrationType: .lightweight(
sourceVersion: currentVersion,
destinationVersion: nextVersion
)
mappingModel: mapping.mappingModel,
migrationType: mapping.migrationType
)
)
}
catch {
return nil
}
}
catch {
return nil
}
currentVersion = nextVersion
}
@@ -804,3 +742,31 @@ public extension DataStack {
}
}
}
// MARK: - FilePrivate
fileprivate extension Array where Element == SchemaMappingProvider {
func findMapping(sourceSchema: DynamicSchema, destinationSchema: DynamicSchema, storage: LocalStorage) throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) {
for element in self {
switch element {
case let element as CustomSchemaMappingProvider
where element.sourceVersion == sourceSchema.modelVersion && element.destinationVersion == destinationSchema.modelVersion:
return try element.createMappingModel(from: sourceSchema, to: destinationSchema, storage: storage)
case let element as XcodeSchemaMappingProvider
where element.sourceVersion == sourceSchema.modelVersion && element.destinationVersion == destinationSchema.modelVersion:
return try element.createMappingModel(from: sourceSchema, to: destinationSchema, storage: storage)
default:
continue
}
}
return try InferredSchemaMappingProvider()
.createMappingModel(from: sourceSchema, to: destinationSchema, storage: storage)
}
}

View File

@@ -1,160 +0,0 @@
//
// MigrationMappingProvider.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 {
var sourceSchema: DynamicSchema { get }
var destinationSchema: DynamicSchema { get }
func createMappingModel() throws -> (mappingModel: NSMappingModel, migrationType: MigrationType)
}
// MARK: - EntityMappingProvider
public protocol EntityMappingProvider {
var source: (schema: DynamicSchema, entity: DynamicEntity) { get }
var destination: (schema: DynamicSchema, entity: DynamicEntity) { get }
func createEntityMapping() -> NSEntityMapping
}
// MARK: - LightweightMappingModelProvider
open class LightweightMappingModelProvider<SourceSchema: DynamicSchema, DestinationSchema: DynamicSchema>: SchemaMappingProvider {
public required init(source: SourceSchema, destination: DestinationSchema) {
self.sourceSchema = source
self.destinationSchema = destination
}
// MARK: SchemaMappingProvider
public let sourceSchema: DynamicSchema
public let destinationSchema: DynamicSchema
public func createMappingModel() throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) {
let sourceModel = self.sourceSchema.rawModel()
let destinationModel = self.destinationSchema.rawModel()
let mappingModel = try NSMappingModel.inferredMappingModel(
forSourceModel: sourceModel,
destinationModel: destinationModel
)
return (
mappingModel,
.lightweight(
sourceVersion: self.sourceSchema.modelVersion,
destinationVersion: self.destinationSchema.modelVersion
)
)
}
}
// MARK: - XcodeMappingModelProvider
open class XcodeMappingModelProvider<SourceSchema: DynamicSchema, DestinationSchema: DynamicSchema>: LightweightMappingModelProvider<SourceSchema, DestinationSchema> {
private let mappingModelBundles: [Bundle]
public required init(source: SourceSchema, destination: DestinationSchema, mappingModelBundles: [Bundle]) {
self.mappingModelBundles = mappingModelBundles
super.init(source: source, destination: destination)
}
public required init(source: SourceSchema, destination: DestinationSchema) {
self.mappingModelBundles = Bundle.allBundles
super.init(source: source, destination: destination)
}
// MARK: SchemaMappingProvider
public override func createMappingModel() throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) {
let sourceModel = self.sourceSchema.rawModel()
let destinationModel = self.destinationSchema.rawModel()
if let mappingModel = NSMappingModel(
from: self.mappingModelBundles,
forSourceModel: sourceModel,
destinationModel: destinationModel) {
return (
mappingModel,
.heavyweight(
sourceVersion: self.sourceSchema.modelVersion,
destinationVersion: self.destinationSchema.modelVersion
)
)
}
return try super.createMappingModel()
}
}
// MARK: - UnsafeMigrationProxyObject
public final class UnsafeMigrationProxyObject {
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
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}

View File

@@ -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
)
}
}

View File

@@ -125,7 +125,7 @@ public final class CSDataStack: NSObject, CoreStoreObjectiveCType {
return bridge(error) {
try self.bridgeToSwift.addStorageAndWait(InMemoryStore.self)
try self.bridgeToSwift.addStorageAndWait(InMemoryStore())
}
}
@@ -143,7 +143,7 @@ public final class CSDataStack: NSObject, CoreStoreObjectiveCType {
return bridge(error) {
try self.bridgeToSwift.addStorageAndWait(SQLiteStore.self)
try self.bridgeToSwift.addStorageAndWait(SQLiteStore())
}
}

View File

@@ -0,0 +1,51 @@
//
// CSDynamicSchema.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: - CSDynamicSchema
/**
The `CSDynamicSchema` serves as the Objective-C bridging type for `DynamicSchema`.
- SeeAlso: `DynamicSchema`
*/
@objc
public protocol CSDynamicSchema {
/**
The version string for this model schema.
*/
@objc
var modelVersion: ModelVersion { get }
/**
Do not call this directly. The `NSManagedObjectModel` for this schema may be created lazily and using this method directly may affect the integrity of the model.
*/
@objc
func rawModel() -> NSManagedObjectModel
}

View File

@@ -0,0 +1,109 @@
//
// CSLegacyXcodeDataModelSchema.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: - CSLegacyXcodeDataModelSchema
/**
The `CSLegacyXcodeDataModelSchema` serves as the Objective-C bridging type for `LegacyXcodeDataModelSchema`.
- SeeAlso: `LegacyXcodeDataModelSchema`
*/
@objc
public final class CSLegacyXcodeDataModelSchema: NSObject, CSDynamicSchema, CoreStoreObjectiveCType {
@objc
public required init(modelName: ModelVersion, model: NSManagedObjectModel) {
self.bridgeToSwift = LegacyXcodeDataModelSchema(
modelName: modelName,
model: model
)
}
// MARK: NSObject
public override var hash: Int {
return ObjectIdentifier(self.bridgeToSwift).hashValue
}
public override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? CSLegacyXcodeDataModelSchema else {
return false
}
return self.bridgeToSwift === object.bridgeToSwift
}
public override var description: String {
return "(\(String(reflecting: type(of: self)))) \(self.bridgeToSwift.coreStoreDumpString)"
}
// MARK: CSDynamicSchema
@objc
public var modelVersion: ModelVersion {
return self.bridgeToSwift.modelVersion
}
@objc
public func rawModel() -> NSManagedObjectModel {
return self.bridgeToSwift.rawModel()
}
// MARK: CoreStoreObjectiveCType
public let bridgeToSwift: LegacyXcodeDataModelSchema
public required init(_ swiftValue: LegacyXcodeDataModelSchema) {
self.bridgeToSwift = swiftValue
super.init()
}
}
// MARK: - LegacyXcodeDataModelSchema
extension LegacyXcodeDataModelSchema: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType
public var bridgeToObjectiveC: CSLegacyXcodeDataModelSchema {
return CSLegacyXcodeDataModelSchema(self)
}
}

View File

@@ -40,19 +40,18 @@ public final class CSSQLiteStore: NSObject, CSLocalStorage, CoreStoreObjectiveCT
/**
Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `CSDataStack`'s `-addStorage*:` methods, a new SQLite file will be created if it does not exist.
- Important: Initializing `CSSQLiteStore`s with custom migration mapping models is currently not supported. Create an `SQLiteStore` instance from Swift code and bridge the instance to Objective-C using its `SQLiteStore.bridgeToObjectiveC` property.
- parameter fileURL: the local file URL for the target SQLite persistent store. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models for migration.
- parameter localStorageOptions: When the `CSSQLiteStore` is passed to the `CSDataStack`'s `addStorage()` methods, tells the `CSDataStack` how to setup the persistent store. Defaults to `CSLocalStorageOptionsNone`.
*/
@objc
public convenience init(fileURL: URL, configuration: ModelConfiguration, mappingModelBundles: [Bundle]?, localStorageOptions: Int) {
public convenience init(fileURL: URL, configuration: ModelConfiguration, localStorageOptions: Int) {
self.init(
SQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles ?? Bundle.allBundles,
localStorageOptions: LocalStorageOptions(rawValue: localStorageOptions)
)
)
@@ -61,29 +60,27 @@ public final class CSSQLiteStore: NSObject, CSLocalStorage, CoreStoreObjectiveCT
/**
Initializes an SQLite store interface from the given SQLite file name. When this instance is passed to the `CSDataStack`'s `-addStorage*:` methods, a new SQLite file will be created if it does not exist.
- Warning: The default SQLite file location for the `CSLegacySQLiteStore` and `CSSQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use `CSLegacySQLiteStore` instead of `CSSQLiteStore`.
- Important: Initializing `CSSQLiteStore`s with custom migration mapping models is currently not supported. Create an `SQLiteStore` instance from Swift code and bridge the instance to Objective-C using its `SQLiteStore.bridgeToObjectiveC` property.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models for migration
- parameter localStorageOptions: When the `CSSQLiteStore` is passed to the `CSDataStack`'s `addStorage()` methods, tells the `CSDataStack` how to setup the persistent store. Defaults to `[CSLocalStorageOptions none]`.
*/
@objc
public convenience init(fileName: String, configuration: ModelConfiguration, mappingModelBundles: [Bundle]?, localStorageOptions: Int) {
public convenience init(fileName: String, configuration: ModelConfiguration, localStorageOptions: Int) {
self.init(
SQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles ?? Bundle.allBundles,
localStorageOptions: LocalStorageOptions(rawValue: localStorageOptions)
)
)
}
/**
Initializes an `CSSQLiteStore` with an all-default settings: a `fileURL` pointing to a "<Application name>.sqlite" file in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS), a `nil` `configuration` pertaining to the "Default" configuration, a `mappingModelBundles` set to search all `NSBundle`s, and `localStorageOptions` set to `.AllowProgresiveMigration`.
Initializes an `CSSQLiteStore` with an all-default settings: a `fileURL` pointing to a "<Application name>.sqlite" file in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS), a `nil` `configuration` pertaining to the "Default" configuration, a `mappingModelBundles` set to search all `NSBundle`s, and `localStorageOptions` set to `[CSLocalStorageOptions none]`.
- Warning: The default SQLite file location for the `CSLegacySQLiteStore` and `CSSQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use `CSLegacySQLiteStore` instead of `CSSQLiteStore`.
- Important: Initializing `CSSQLiteStore`s with custom migration mapping models is currently not supported. Create an `SQLiteStore` instance from Swift code and bridge the instance to Objective-C using its `SQLiteStore.bridgeToObjectiveC` property.
*/
@objc
public convenience override init() {
@@ -104,12 +101,12 @@ public final class CSSQLiteStore: NSObject, CSLocalStorage, CoreStoreObjectiveCT
}
/**
The `NSBundle`s from which to search mapping models for migrations
An array of `SchemaMappingProvider`s that provides the complete mapping models for custom migrations. This is currently only supported for Swift code.
*/
@objc
public var mappingModelBundles: [Bundle] {
return self.bridgeToSwift.mappingModelBundles
public var migrationMappingProviders: [Any] {
return self.bridgeToSwift.migrationMappingProviders
}
/**
@@ -194,6 +191,55 @@ public final class CSSQLiteStore: NSObject, CSLocalStorage, CoreStoreObjectiveCT
self.bridgeToSwift = swiftValue
super.init()
}
// MARK: Deprecated
/**
Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `CSDataStack`'s `-addStorage*:` methods, a new SQLite file will be created if it does not exist.
- Important: Initializing `CSSQLiteStore`s with custom migration mapping models is currently not supported. Create an `SQLiteStore` instance from Swift code and bridge the instance to Objective-C using its `SQLiteStore.bridgeToObjectiveC` property.
- parameter fileURL: the local file URL for the target SQLite persistent store. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models for migration.
- parameter localStorageOptions: When the `CSSQLiteStore` is passed to the `CSDataStack`'s `addStorage()` methods, tells the `CSDataStack` how to setup the persistent store. Defaults to `CSLocalStorageOptionsNone`.
*/
@available(*, deprecated: 3.1, message: "The `mappingModelBundles` argument of this method is ignored. Use the new -[CSSQLiteStore initWithFileURL:configuration:localStorageOptions:]) initializer instead.")
@objc
public convenience init(fileURL: URL, configuration: ModelConfiguration, mappingModelBundles: [Bundle]?, localStorageOptions: Int) {
self.init(
SQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles ?? Bundle.allBundles,
localStorageOptions: LocalStorageOptions(rawValue: localStorageOptions)
)
)
}
/**
Initializes an SQLite store interface from the given SQLite file name. When this instance is passed to the `CSDataStack`'s `-addStorage*:` methods, a new SQLite file will be created if it does not exist.
- Important: Initializing `CSSQLiteStore`s with custom migration mapping models is currently not supported. Create an `SQLiteStore` instance from Swift code and bridge the instance to Objective-C using its `SQLiteStore.bridgeToObjectiveC` property.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models for migration
- parameter localStorageOptions: When the `CSSQLiteStore` is passed to the `CSDataStack`'s `addStorage()` methods, tells the `CSDataStack` how to setup the persistent store. Defaults to `[CSLocalStorageOptions none]`.
*/
@available(*, deprecated: 3.1, message: "The `mappingModelBundles` argument of this method is ignored. Use the new -[CSSQLiteStore initWithFileName:configuration:localStorageOptions:]) initializer instead.")
@objc
public convenience init(fileName: String, configuration: ModelConfiguration, mappingModelBundles: [Bundle]?, localStorageOptions: Int) {
self.init(
SQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles ?? Bundle.allBundles,
localStorageOptions: LocalStorageOptions(rawValue: localStorageOptions)
)
)
}
}

View File

@@ -106,10 +106,10 @@ public protocol CSLocalStorage: CSStorageInterface {
var fileURL: URL { get }
/**
The `NSBundle`s from which to search mapping models for migrations
An array of `SchemaMappingProvider`s that provides the complete mapping models for custom migrations. This is currently only supported for Swift code.
*/
@objc
var mappingModelBundles: [Bundle] { get }
var migrationMappingProviders: [Any] { get }
/**
Options that tell the `CSDataStack` how to setup the persistent store

View File

@@ -0,0 +1,109 @@
//
// CSXcodeDataModelSchema.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: - CSXcodeDataModelSchema
/**
The `CSXcodeDataModelSchema` serves as the Objective-C bridging type for `XcodeDataModelSchema`.
- SeeAlso: `XcodeDataModelSchema`
*/
@objc
public final class CSXcodeDataModelSchema: NSObject, CSDynamicSchema, CoreStoreObjectiveCType {
@objc
public required init(modelVersion: ModelVersion, modelVersionFileURL: URL) {
self.bridgeToSwift = XcodeDataModelSchema(
modelVersion: modelVersion,
modelVersionFileURL: modelVersionFileURL
)
}
// MARK: NSObject
public override var hash: Int {
return ObjectIdentifier(self.bridgeToSwift).hashValue
}
public override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? CSXcodeDataModelSchema else {
return false
}
return self.bridgeToSwift === object.bridgeToSwift
}
public override var description: String {
return "(\(String(reflecting: type(of: self)))) \(self.bridgeToSwift.coreStoreDumpString)"
}
// MARK: CSDynamicSchema
@objc
public var modelVersion: ModelVersion {
return self.bridgeToSwift.modelVersion
}
@objc
public func rawModel() -> NSManagedObjectModel {
return self.bridgeToSwift.rawModel()
}
// MARK: CoreStoreObjectiveCType
public let bridgeToSwift: XcodeDataModelSchema
public required init(_ swiftValue: XcodeDataModelSchema) {
self.bridgeToSwift = swiftValue
super.init()
}
}
// MARK: - XcodeDataModelSchema
extension XcodeDataModelSchema: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType
public var bridgeToObjectiveC: CSXcodeDataModelSchema {
return CSXcodeDataModelSchema(self)
}
}

View File

@@ -76,6 +76,11 @@ internal func bridge<T: CoreStoreSwiftType>(_ closure: () -> T) -> T.ObjectiveCT
return closure().bridgeToObjectiveC
}
internal func bridge<T: CoreStoreSwiftType>(_ closure: () -> [T]) -> [T.ObjectiveCType] {
return closure().map { $0.bridgeToObjectiveC }
}
internal func bridge<T: CoreStoreSwiftType>(_ closure: () -> T?) -> T.ObjectiveCType? {
return closure()?.bridgeToObjectiveC

View File

@@ -81,22 +81,7 @@ public extension CoreStore {
@discardableResult
public static func addStorageAndWait() throws -> SQLiteStore {
return try self.defaultStack.addStorageAndWait(SQLiteStore.self)
}
/**
Creates a `StorageInterface` of the specified store type with default values and adds it to the `defaultStack`. This method blocks until completion.
```
try CoreStore.addStorageAndWait(InMemoryStore.self)
```
- parameter storeType: the `StorageInterface` type
- throws: a `CoreStoreError` value indicating the failure
- returns: the `StorageInterface` added to the `defaultStack`
*/
@discardableResult
public static func addStorageAndWait<T: StorageInterface>(_ storeType: T.Type) throws -> T where T: DefaultInitializableStore {
return try self.defaultStack.addStorageAndWait(storeType.init())
return try self.defaultStack.addStorageAndWait(SQLiteStore())
}
/**
@@ -114,21 +99,6 @@ public extension CoreStore {
return try self.defaultStack.addStorageAndWait(storage)
}
/**
Creates a `LocalStorageInterface` of the specified store type with default values and adds it to the `defaultStack`. This method blocks until completion.
```
try CoreStore.addStorageAndWait(SQLiteStore.self)
```
- parameter storeType: the `LocalStorageInterface` type
- throws: a `CoreStoreError` value indicating the failure
- returns: the local storage added to the `defaultStack`
*/
@discardableResult
public static func addStorageAndWait<T: LocalStorage>(_ storageType: T.Type) throws -> T where T: DefaultInitializableStore {
return try self.defaultStack.addStorageAndWait(storageType.init())
}
/**
Adds a `LocalStorage` to the `defaultStack` and blocks until completion.
```

View File

@@ -142,7 +142,7 @@ public final class DataStack: Equatable {
continue
case .coreStore:
guard let anyEntity = entityDescription.anyEntity else {
guard let anyEntity = entityDescription.coreStoreEntity else {
continue
}
@@ -191,22 +191,7 @@ public final class DataStack: Equatable {
@discardableResult
public func addStorageAndWait() throws -> SQLiteStore {
return try self.addStorageAndWait(SQLiteStore.self)
}
/**
Creates a `StorageInterface` of the specified store type with default values and adds it to the stack. This method blocks until completion.
```
try dataStack.addStorageAndWait(InMemoryStore.self)
```
- parameter storeType: the `StorageInterface` type
- throws: a `CoreStoreError` value indicating the failure
- returns: the `StorageInterface` added to the stack
*/
@discardableResult
public func addStorageAndWait<T: StorageInterface>(_ storeType: T.Type) throws -> T where T: DefaultInitializableStore {
return try self.addStorageAndWait(storeType.init())
return try self.addStorageAndWait(SQLiteStore())
}
/**
@@ -248,21 +233,6 @@ public final class DataStack: Equatable {
}
}
/**
Creates a `LocalStorageInterface` of the specified store type with default values and adds it to the stack. This method blocks until completion.
```
try dataStack.addStorageAndWait(SQLiteStore.self)
```
- parameter storeType: the `LocalStorageInterface` type
- throws: a `CoreStoreError` value indicating the failure
- returns: the local storage added to the stack
*/
@discardableResult
public func addStorageAndWait<T: LocalStorage>(_ storageType: T.Type) throws -> T where T: DefaultInitializableStore {
return try self.addStorageAndWait(storageType.init())
}
/**
Adds a `LocalStorage` to the stack and blocks until completion.
```

View File

@@ -203,7 +203,7 @@ public final class CoreStoreSchema: DynamicSchema {
private static func firstPassCreateEntityDescription(from entity: AnyEntity) -> NSEntityDescription {
let entityDescription = NSEntityDescription()
entityDescription.anyEntity = entity
entityDescription.coreStoreEntity = entity
entityDescription.name = entity.entityName
entityDescription.isAbstract = entity.isAbstract
entityDescription.managedObjectClassName = NSStringFromClass(NSManagedObject.self)

View File

@@ -223,6 +223,11 @@ public final class SchemaHistory: ExpressibleByArrayLiteral {
return self.schemaByVersion[modelVersion]?.rawModel()
}
internal func schema(for modelVersion: ModelVersion) -> DynamicSchema? {
return self.schemaByVersion[modelVersion]
}
internal func schema(for storeMetadata: [String: Any]) -> DynamicSchema? {
guard let modelHashes = storeMetadata[NSStoreModelVersionHashesKey] as! [String: Data]? else {

View File

@@ -31,7 +31,7 @@ import CoreData
/**
A storage interface that is backed only by memory.
*/
public final class InMemoryStore: StorageInterface, DefaultInitializableStore {
public final class InMemoryStore: StorageInterface {
/**
Initializes an `InMemoryStore` for the specified configuration
@@ -42,9 +42,6 @@ public final class InMemoryStore: StorageInterface, DefaultInitializableStore {
self.configuration = configuration
}
// MARK: DefaultInitializableStore
/**
Initializes an `InMemoryStore` with the "Default" configuration
*/

View File

@@ -34,7 +34,8 @@ import CoreData
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use `LegacySQLiteStore` instead of `SQLiteStore`.
*/
public final class LegacySQLiteStore: LocalStorage, DefaultInitializableStore {
@available(*, obsoleted: 3.1, message: "`LegacySQLiteStore` previous users should now use `SQLiteStore`'s new SQLiteStore.legacy(fileName:configuration:migrationMappingProviders:localStorageOptions:) or SQLiteStore.legacy() methods to create an `SQLiteStore` with legacy paths.")
public final class LegacySQLiteStore: LocalStorage {
/**
Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist.
@@ -44,12 +45,10 @@ public final class LegacySQLiteStore: LocalStorage, DefaultInitializableStore {
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models for migration.
- parameter localStorageOptions: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, tells the `DataStack` how to setup the persistent store. Defaults to `.None`.
*/
@available(*, obsoleted: 3.1, message: "Use `SQLiteStore`'s new SQLiteStore.init(fileURL:configuration:migrationMappingProviders:localStorageOptions:) initializer")
public init(fileURL: URL, configuration: ModelConfiguration = nil, mappingModelBundles: [Bundle] = Bundle.allBundles, localStorageOptions: LocalStorageOptions = nil) {
self.fileURL = fileURL
self.configuration = configuration
self.mappingModelBundles = mappingModelBundles
self.localStorageOptions = localStorageOptions
fatalError()
}
/**
@@ -61,31 +60,21 @@ public final class LegacySQLiteStore: LocalStorage, DefaultInitializableStore {
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models for migration.
- parameter localStorageOptions: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, tells the `DataStack` how to setup the persistent store. Defaults to `.None`.
*/
@available(*, obsoleted: 3.1, message: "Use `SQLiteStore`'s new SQLiteStore.legacy(fileName:configuration:migrationMappingProviders:localStorageOptions:) method.")
public init(fileName: String, configuration: ModelConfiguration = nil, mappingModelBundles: [Bundle] = Bundle.allBundles, localStorageOptions: LocalStorageOptions = nil) {
self.fileURL = LegacySQLiteStore.defaultRootDirectory.appendingPathComponent(
fileName,
isDirectory: false
)
self.configuration = configuration
self.mappingModelBundles = mappingModelBundles
self.localStorageOptions = localStorageOptions
fatalError()
}
// MARK: DefaultInitializableStore
/**
Initializes an `LegacySQLiteStore` with an all-default settings: a `fileURL` pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS), a `nil` `configuration` pertaining to the "Default" configuration, a `mappingModelBundles` set to search all `NSBundle`s, and `localStorageOptions` set to `.AllowProgresiveMigration`.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use `LegacySQLiteStore` instead of `SQLiteStore`.
*/
@available(*, obsoleted: 3.1, message: "Use `SQLiteStore`'s new SQLiteStore.legacy() method.")
public init() {
self.fileURL = LegacySQLiteStore.defaultFileURL
self.configuration = nil
self.mappingModelBundles = Bundle.allBundles
self.localStorageOptions = nil
fatalError()
}
@@ -153,9 +142,9 @@ public final class LegacySQLiteStore: LocalStorage, DefaultInitializableStore {
public let fileURL: URL
/**
The `NSBundle`s from which to search mapping models for migrations
An array of `SchemaMappingProviders` that provides the complete mapping models for custom migrations.
*/
public let mappingModelBundles: [Bundle]
public let migrationMappingProviders: [SchemaMappingProvider]
/**
Options that tell the `DataStack` how to setup the persistent store
@@ -231,26 +220,6 @@ public final class LegacySQLiteStore: LocalStorage, DefaultInitializableStore {
}
// MARK: Internal
internal static let defaultRootDirectory: URL = cs_lazy {
#if os(tvOS)
let systemDirectorySearchPath = FileManager.SearchPathDirectory.cachesDirectory
#else
let systemDirectorySearchPath = FileManager.SearchPathDirectory.applicationSupportDirectory
#endif
return FileManager.default.urls(
for: systemDirectorySearchPath,
in: .userDomainMask).first!
}
internal static let defaultFileURL = LegacySQLiteStore.defaultRootDirectory
.appendingPathComponent(DataStack.applicationName, isDirectory: false)
.appendingPathExtension("sqlite")
// MARK: Private
private weak var dataStack: DataStack?

View File

@@ -33,21 +33,21 @@ import CoreData
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use `LegacySQLiteStore` instead of `SQLiteStore`.
*/
public final class SQLiteStore: LocalStorage, DefaultInitializableStore {
public final class SQLiteStore: LocalStorage {
/**
Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist.
- parameter fileURL: the local file URL for the target SQLite persistent store. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models (*.xcmappingmodel) for migration.
- parameter migrationMappingProviders: An array of `SchemaMappingProviders` that provides the complete mapping models for custom migrations. All lightweight inferred mappings and/or migration mappings provided by *xcmappingmodel files are automatically used as fallback (as `InferredSchemaMappingProvider`) and may be omitted from the array.
- parameter localStorageOptions: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, tells the `DataStack` how to setup the persistent store. Defaults to `.None`.
*/
public init(fileURL: URL, configuration: ModelConfiguration = nil, mappingModelBundles: [Bundle] = Bundle.allBundles, localStorageOptions: LocalStorageOptions = nil) {
public init(fileURL: URL, configuration: ModelConfiguration = nil, migrationMappingProviders: [SchemaMappingProvider] = [], localStorageOptions: LocalStorageOptions = nil) {
self.fileURL = fileURL
self.configuration = configuration
self.mappingModelBundles = mappingModelBundles
self.migrationMappingProviders = migrationMappingProviders
self.localStorageOptions = localStorageOptions
}
@@ -57,21 +57,18 @@ public final class SQLiteStore: LocalStorage, DefaultInitializableStore {
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use `LegacySQLiteStore` instead of `SQLiteStore`.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models (*.xcmappingmodel) for migration
- parameter migrationMappingProviders: An array of `SchemaMappingProviders` that provides the complete mapping models for custom migrations. All lightweight inferred mappings and/or migration mappings provided by *xcmappingmodel files are automatically used as fallback (as `InferredSchemaMappingProvider`) and may be omitted from the array.
- parameter localStorageOptions: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, tells the `DataStack` how to setup the persistent store. Defaults to `.None`.
*/
public init(fileName: String, configuration: ModelConfiguration = nil, mappingModelBundles: [Bundle] = Bundle.allBundles, localStorageOptions: LocalStorageOptions = nil) {
public init(fileName: String, configuration: ModelConfiguration = nil, migrationMappingProviders: [SchemaMappingProvider] = [], localStorageOptions: LocalStorageOptions = nil) {
self.fileURL = SQLiteStore.defaultRootDirectory
.appendingPathComponent(fileName, isDirectory: false)
self.configuration = configuration
self.mappingModelBundles = mappingModelBundles
self.migrationMappingProviders = migrationMappingProviders
self.localStorageOptions = localStorageOptions
}
// MARK: DefaultInitializableStore
/**
Initializes an `SQLiteStore` with an all-default settings: a `fileURL` pointing to a "<Application name>.sqlite" file in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS), a `nil` `configuration` pertaining to the "Default" configuration, a `mappingModelBundles` set to search all `NSBundle`s, and `localStorageOptions` set to `.AllowProgresiveMigration`.
@@ -81,10 +78,45 @@ public final class SQLiteStore: LocalStorage, DefaultInitializableStore {
self.fileURL = SQLiteStore.defaultFileURL
self.configuration = nil
self.mappingModelBundles = Bundle.allBundles
self.migrationMappingProviders = []
self.localStorageOptions = nil
}
/**
Initializes an SQLite store interface from the given SQLite file name. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use `LegacySQLiteStore` instead of `SQLiteStore`.
- parameter legacyFileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter migrationMappingProviders: An array of `SchemaMappingProviders` that provides the complete mapping models for custom migrations. All lightweight inferred mappings and/or migration mappings provided by *xcmappingmodel files are automatically used as fallback (as `InferredSchemaMappingProvider`) and may be omitted from the array.
- parameter localStorageOptions: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, tells the `DataStack` how to setup the persistent store. Defaults to `.None`.
*/
public static func legacy(fileName: String, configuration: ModelConfiguration = nil, migrationMappingProviders: [SchemaMappingProvider] = [], localStorageOptions: LocalStorageOptions = nil) -> SQLiteStore {
return SQLiteStore(
fileURL: SQLiteStore.legacyDefaultRootDirectory
.appendingPathComponent(fileName, isDirectory: false),
configuration: configuration,
migrationMappingProviders: migrationMappingProviders,
localStorageOptions: localStorageOptions
)
}
/**
Initializes an `LegacySQLiteStore` with an all-default settings: a `fileURL` pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS), a `nil` `configuration` pertaining to the "Default" configuration, a `mappingModelBundles` set to search all `NSBundle`s, and `localStorageOptions` set to `.AllowProgresiveMigration`.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use `LegacySQLiteStore` instead of `SQLiteStore`.
*/
public static func legacy() -> SQLiteStore {
return SQLiteStore(
fileURL: SQLiteStore.legacyDefaultFileURL,
configuration: nil,
migrationMappingProviders: [],
localStorageOptions: nil
)
}
// MARK: StorageInterface
@@ -131,9 +163,9 @@ public final class SQLiteStore: LocalStorage, DefaultInitializableStore {
public let fileURL: URL
/**
The `NSBundle`s from which to search mapping models for migrations
An array of `SchemaMappingProviders` that provides the complete mapping models for custom migrations.
*/
public let mappingModelBundles: [Bundle]
public let migrationMappingProviders: [SchemaMappingProvider]
/**
Options that tell the `DataStack` how to setup the persistent store
@@ -255,8 +287,60 @@ public final class SQLiteStore: LocalStorage, DefaultInitializableStore {
)
.appendingPathExtension("sqlite")
internal static let legacyDefaultRootDirectory: URL = cs_lazy {
#if os(tvOS)
let systemDirectorySearchPath = FileManager.SearchPathDirectory.cachesDirectory
#else
let systemDirectorySearchPath = FileManager.SearchPathDirectory.applicationSupportDirectory
#endif
return FileManager.default.urls(
for: systemDirectorySearchPath,
in: .userDomainMask).first!
}
internal static let legacyDefaultFileURL = cs_lazy {
return SQLiteStore.legacyDefaultRootDirectory
.appendingPathComponent(DataStack.applicationName, isDirectory: false)
.appendingPathExtension("sqlite")
}
// MARK: Private
private weak var dataStack: DataStack?
// MARK: Deprecated
/**
Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist.
- parameter fileURL: the local file URL for the target SQLite persistent store. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models (*.xcmappingmodel) for migration.
- parameter localStorageOptions: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, tells the `DataStack` how to setup the persistent store. Defaults to `.None`.
*/
@available(*, deprecated: 3.1, message: "The `mappingModelBundles` argument of this method is ignored. Use the new SQLiteStore.init(fileURL:configuration:migrationMappingProviders:localStorageOptions:) initializer instead.")
public convenience init(fileURL: URL, configuration: ModelConfiguration = nil, mappingModelBundles: [Bundle], localStorageOptions: LocalStorageOptions = nil) {
self.init(fileURL: fileURL, configuration: configuration, migrationMappingProviders: [], localStorageOptions: localStorageOptions)
}
/**
Initializes an SQLite store interface from the given SQLite file name. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use `LegacySQLiteStore` instead of `SQLiteStore`.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: a list of `NSBundle`s from which to search mapping models (*.xcmappingmodel) for migration.
- parameter localStorageOptions: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, tells the `DataStack` how to setup the persistent store. Defaults to `.None`.
*/
@available(*, deprecated: 3.1, message: "The `mappingModelBundles` argument of this method is ignored. Use the new SQLiteStore.init(fileName:configuration:migrationMappingProviders:localStorageOptions:) initializer instead.")
public convenience init(fileName: String, configuration: ModelConfiguration = nil, mappingModelBundles: [Bundle], localStorageOptions: LocalStorageOptions = nil) {
self.init(fileName: fileName, configuration: configuration, migrationMappingProviders: [], localStorageOptions: localStorageOptions)
}
}

View File

@@ -63,20 +63,6 @@ public protocol StorageInterface: class {
}
// MARK: - DefaultInitializableStore
/**
The `DefaultInitializableStore` represents `StorageInterface`s that can be initialized with default values
*/
public protocol DefaultInitializableStore: StorageInterface {
/**
Initializes the `StorageInterface` with the default configurations
*/
init()
}
// MARK: - LocalStorageOptions
/**
@@ -141,9 +127,9 @@ public protocol LocalStorage: StorageInterface {
var fileURL: URL { get }
/**
The `NSBundle`s from which to search mapping models (*.xcmappingmodel) for migrations
An array of `SchemaMappingProvider`s that provides the complete mapping models for custom migrations.
*/
var mappingModelBundles: [Bundle] { get }
var migrationMappingProviders: [SchemaMappingProvider] { get }
/**
Options that tell the `DataStack` how to setup the persistent store