mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-21 00:49:20 +01:00
WIP: custom migration
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -32,7 +32,7 @@ import Foundation
|
||||
internal extension NSEntityDescription {
|
||||
|
||||
@nonobjc
|
||||
internal var anyEntity: CoreStoreSchema.AnyEntity? {
|
||||
internal var coreStoreEntity: CoreStoreSchema.AnyEntity? {
|
||||
|
||||
get {
|
||||
|
||||
|
||||
55
Sources/Internal/NSEntityDescription+Migration.swift
Normal file
55
Sources/Internal/NSEntityDescription+Migration.swift
Normal 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
|
||||
}
|
||||
}
|
||||
44
Sources/Internal/NSManagedObjectModel+Migration.swift
Normal file
44
Sources/Internal/NSManagedObjectModel+Migration.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
51
Sources/ObjectiveC/CSDynamicSchema.swift
Normal file
51
Sources/ObjectiveC/CSDynamicSchema.swift
Normal 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
|
||||
}
|
||||
109
Sources/ObjectiveC/CSLegacyXcodeDataModelSchema.swift
Normal file
109
Sources/ObjectiveC/CSLegacyXcodeDataModelSchema.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
109
Sources/ObjectiveC/CSXcodeDataModelSchema.swift
Normal file
109
Sources/ObjectiveC/CSXcodeDataModelSchema.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user