From a117eeee489ba9f201c7922dfec5f17194252180 Mon Sep 17 00:00:00 2001 From: Pavel Koltsov Date: Thu, 1 Jun 2017 16:42:54 +0500 Subject: [PATCH 1/3] Progressive migration --- DataKernel.xcodeproj/project.pbxproj | 148 ++++++++++++++++++ .../Classes/Contracts/DKStoreLoader.swift | 6 + .../CoreData/CoreDataLocalStorage.swift | 32 ++-- .../CoreData/DKStandardStoreLoader.swift | 14 ++ .../Classes/Migration/DKMigration.swift | 61 ++++++++ .../Classes/Migration/DKMigrationError.swift | 7 + .../Migration/DKMigrationFactory.swift | 43 +++++ DataKernel/Classes/Migration/DKModels.swift | 48 ++++++ .../Migration/DKProgressiveStoreLoader.swift | 42 +++++ .../Classes/Migration/DKStoreFile.swift | 31 ++++ .../Classes/Migration/DKVersionPolicy.swift | 17 ++ .../xcmapping.xml | 83 ++++++++++ .../.xccurrentversion | 8 + .../AnotherTestModel 2.xcdatamodel/contents | 11 ++ .../AnotherTestModel.xcdatamodel/contents | 10 ++ .../Classes/Migration/DKMigrationTests.swift | 41 +++++ .../Classes/Migration/DKModelsTests.swift | 14 ++ .../DKProgressiveStoreLoaderTests.swift | 54 +++++++ .../Classes/Migration/DKStoreFileTests.swift | 25 +++ .../DKVersionFromFileNamePolicyTests.swift | 31 ++++ .../Migration/FileManager+DKTests.swift | 12 ++ .../Model.xcdatamodeld/.xccurrentversion | 8 + .../Model 2.xcdatamodel/contents | 9 ++ .../Model 3.xcdatamodel/contents | 10 ++ .../Model.xcdatamodel/contents | 9 ++ .../ModelMapping.xcmappingmodel/xcmapping.xml | 77 +++++++++ .../Classes/Migration/TestData.swift | 37 +++++ .../Migration/TestModelV2ToV3Mapping.swift | 19 +++ 28 files changed, 890 insertions(+), 17 deletions(-) create mode 100644 DataKernel/Classes/Contracts/DKStoreLoader.swift create mode 100644 DataKernel/Classes/CoreData/DKStandardStoreLoader.swift create mode 100644 DataKernel/Classes/Migration/DKMigration.swift create mode 100644 DataKernel/Classes/Migration/DKMigrationError.swift create mode 100644 DataKernel/Classes/Migration/DKMigrationFactory.swift create mode 100644 DataKernel/Classes/Migration/DKModels.swift create mode 100644 DataKernel/Classes/Migration/DKProgressiveStoreLoader.swift create mode 100644 DataKernel/Classes/Migration/DKStoreFile.swift create mode 100644 DataKernel/Classes/Migration/DKVersionPolicy.swift create mode 100644 DataKernelTests/Classes/Migration/AnotherModelMapping.xcmappingmodel/xcmapping.xml create mode 100644 DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/.xccurrentversion create mode 100644 DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel 2.xcdatamodel/contents create mode 100644 DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel.xcdatamodel/contents create mode 100644 DataKernelTests/Classes/Migration/DKMigrationTests.swift create mode 100644 DataKernelTests/Classes/Migration/DKModelsTests.swift create mode 100644 DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift create mode 100644 DataKernelTests/Classes/Migration/DKStoreFileTests.swift create mode 100644 DataKernelTests/Classes/Migration/DKVersionFromFileNamePolicyTests.swift create mode 100644 DataKernelTests/Classes/Migration/FileManager+DKTests.swift create mode 100644 DataKernelTests/Classes/Migration/Model.xcdatamodeld/.xccurrentversion create mode 100644 DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 2.xcdatamodel/contents create mode 100644 DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 3.xcdatamodel/contents create mode 100644 DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model.xcdatamodel/contents create mode 100644 DataKernelTests/Classes/Migration/ModelMapping.xcmappingmodel/xcmapping.xml create mode 100644 DataKernelTests/Classes/Migration/TestData.swift create mode 100644 DataKernelTests/Classes/Migration/TestModelV2ToV3Mapping.swift diff --git a/DataKernel.xcodeproj/project.pbxproj b/DataKernel.xcodeproj/project.pbxproj index 669ef61..63c10fe 100644 --- a/DataKernel.xcodeproj/project.pbxproj +++ b/DataKernel.xcodeproj/project.pbxproj @@ -7,6 +7,26 @@ objects = { /* Begin PBXBuildFile section */ + 8AFF0AD21EDC30AE004A8714 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9E58C1FF815624DBA8F2 /* Model.xcdatamodeld */; }; + 8AFF0AD31EDC30B9004A8714 /* TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA92D2C53985DAF69565A0 /* TestData.swift */; }; + 8AFF0AD41EDC30B9004A8714 /* TestModelV2ToV3Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9BDD0BBD54DE9E24505B /* TestModelV2ToV3Mapping.swift */; }; + 8AFF0AD51EDC30B9004A8714 /* DKProgressiveStoreLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA96EBD15390FD9BCD9D03 /* DKProgressiveStoreLoaderTests.swift */; }; + 8AFF0AD61EDC30B9004A8714 /* DKModelsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9AEF2F0376F0FA6D8577 /* DKModelsTests.swift */; }; + 8AFF0AD71EDC30BF004A8714 /* DKMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA94286DF73003A70255BB /* DKMigrationTests.swift */; }; + 8AFF0AD81EDC30BF004A8714 /* DKStoreFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA917D1C5413697A29EFC3 /* DKStoreFileTests.swift */; }; + 8AFF0AD91EDC30BF004A8714 /* FileManager+DKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA98CB6D44FF6A0288FC89 /* FileManager+DKTests.swift */; }; + 8AFF0ADA1EDC30C4004A8714 /* AnotherTestModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9C7AEEC132E986272C8E /* AnotherTestModel.xcdatamodeld */; }; + 8AFF0ADB1EDC30CC004A8714 /* DKVersionFromFileNamePolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA93B04BA179457F7FB22E /* DKVersionFromFileNamePolicyTests.swift */; }; + 8AFF0AE01EDC32E8004A8714 /* AnotherModelMapping.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 8AFF0ADF1EDC32E8004A8714 /* AnotherModelMapping.xcmappingmodel */; }; + 8AFF0AE11EDC3392004A8714 /* AnotherModelMapping.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 8AFF0ADF1EDC32E8004A8714 /* AnotherModelMapping.xcmappingmodel */; }; + 8AFF0AE31EDC344F004A8714 /* ModelMapping.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 8AFF0AE21EDC344F004A8714 /* ModelMapping.xcmappingmodel */; }; + 8AFF0AE41EDD6ED6004A8714 /* DKMigrationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA99D5EB83BC0E9015C313 /* DKMigrationError.swift */; }; + 8AFF0AE51EDD6ED6004A8714 /* DKProgressiveStoreLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9AB0036EBA3C7F88DA04 /* DKProgressiveStoreLoader.swift */; }; + 8AFF0AE61EDD6ED6004A8714 /* DKModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA969BF1FDB0C4812FB0C6 /* DKModels.swift */; }; + 8AFF0AE71EDD6ED6004A8714 /* DKMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9346FEF8FCB055D4780C /* DKMigration.swift */; }; + 8AFF0AE81EDD6ED6004A8714 /* DKStoreFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA925DB1761FEA5B969535 /* DKStoreFile.swift */; }; + 8AFF0AE91EDD6ED6004A8714 /* DKVersionPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9812AEBFAC3BA2A088E0 /* DKVersionPolicy.swift */; }; + 8AFF0AEA1EDD6ED6004A8714 /* DKMigrationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA90C90848DB05D6FF4B78 /* DKMigrationFactory.swift */; }; 945D44A71CD9012E00A47743 /* CoreDataContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D44A61CD9012E00A47743 /* CoreDataContextTests.swift */; }; 9461A3B01CD7AA19008EEC3C /* NSManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9461A3AF1CD7AA19008EEC3C /* NSManagedObject.swift */; }; 9461A3B41CD7D1A0008EEC3C /* DataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 9461A3B21CD7D1A0008EEC3C /* DataModel.xcdatamodeld */; }; @@ -49,6 +69,18 @@ 94F74AAE1CD53D2D000F1F4D /* DkErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F74AAD1CD53D2D000F1F4D /* DkErrors.swift */; }; 94F74AB01CD53E33000F1F4D /* ContextRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F74AAF1CD53E33000F1F4D /* ContextRef.swift */; }; 94F74AB21CD53F04000F1F4D /* NSManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F74AB11CD53F04000F1F4D /* NSManagedObjectContext.swift */; }; + D9EA93F718F4B1FE60ED9ADA /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9E58C1FF815624DBA8F2 /* Model.xcdatamodeld */; }; + D9EA9435C6D9E1A95035EB40 /* DKModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA969BF1FDB0C4812FB0C6 /* DKModels.swift */; }; + D9EA959ED577EB336DA4CD6C /* DKVersionPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9812AEBFAC3BA2A088E0 /* DKVersionPolicy.swift */; }; + D9EA961A6EC506D5015D53DC /* DKStoreLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9E28AE0E92A2032A17C6 /* DKStoreLoader.swift */; }; + D9EA9A0FBEDF3BE12C97AA15 /* DKStandardStoreLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA936F8C0B61D5936A229F /* DKStandardStoreLoader.swift */; }; + D9EA9B02719AA217097EF74B /* DKStoreFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA925DB1761FEA5B969535 /* DKStoreFile.swift */; }; + D9EA9B0B0E741649473CD8B3 /* DKMigrationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA90C90848DB05D6FF4B78 /* DKMigrationFactory.swift */; }; + D9EA9B3FD126D79F83711B44 /* DKMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9346FEF8FCB055D4780C /* DKMigration.swift */; }; + D9EA9C1CFB6F5BC5857D7FB5 /* DKStoreLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9E28AE0E92A2032A17C6 /* DKStoreLoader.swift */; }; + D9EA9C35411474B1E4C90D74 /* DKMigrationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA99D5EB83BC0E9015C313 /* DKMigrationError.swift */; }; + D9EA9E3FFE6B2CED9CCD50A3 /* DKProgressiveStoreLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA9AB0036EBA3C7F88DA04 /* DKProgressiveStoreLoader.swift */; }; + D9EA9E7F6E3C8C1C85BFE675 /* DKStandardStoreLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA936F8C0B61D5936A229F /* DKStandardStoreLoader.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -62,6 +94,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 8AFF0ADF1EDC32E8004A8714 /* AnotherModelMapping.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AnotherModelMapping.xcmappingmodel; sourceTree = ""; }; + 8AFF0AE21EDC344F004A8714 /* ModelMapping.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = ModelMapping.xcmappingmodel; sourceTree = ""; }; 945D44A61CD9012E00A47743 /* CoreDataContextTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataContextTests.swift; sourceTree = ""; }; 9461A3AF1CD7AA19008EEC3C /* NSManagedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSManagedObject.swift; sourceTree = ""; }; 9461A3B31CD7D1A0008EEC3C /* DataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DataModel.xcdatamodel; sourceTree = ""; }; @@ -97,6 +131,28 @@ 94F74AAD1CD53D2D000F1F4D /* DkErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DkErrors.swift; sourceTree = ""; }; 94F74AAF1CD53E33000F1F4D /* ContextRef.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextRef.swift; sourceTree = ""; }; 94F74AB11CD53F04000F1F4D /* NSManagedObjectContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSManagedObjectContext.swift; sourceTree = ""; }; + D9EA90C90848DB05D6FF4B78 /* DKMigrationFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKMigrationFactory.swift; sourceTree = ""; }; + D9EA917D1C5413697A29EFC3 /* DKStoreFileTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKStoreFileTests.swift; sourceTree = ""; }; + D9EA925DB1761FEA5B969535 /* DKStoreFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKStoreFile.swift; sourceTree = ""; }; + D9EA92D2C53985DAF69565A0 /* TestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestData.swift; sourceTree = ""; }; + D9EA9346FEF8FCB055D4780C /* DKMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKMigration.swift; sourceTree = ""; }; + D9EA936F8C0B61D5936A229F /* DKStandardStoreLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKStandardStoreLoader.swift; sourceTree = ""; }; + D9EA93B04BA179457F7FB22E /* DKVersionFromFileNamePolicyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKVersionFromFileNamePolicyTests.swift; sourceTree = ""; }; + D9EA94286DF73003A70255BB /* DKMigrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKMigrationTests.swift; sourceTree = ""; }; + D9EA969BF1FDB0C4812FB0C6 /* DKModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKModels.swift; sourceTree = ""; }; + D9EA96EBD15390FD9BCD9D03 /* DKProgressiveStoreLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKProgressiveStoreLoaderTests.swift; sourceTree = ""; }; + D9EA9812AEBFAC3BA2A088E0 /* DKVersionPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKVersionPolicy.swift; sourceTree = ""; }; + D9EA981897B6D9F2E55419F3 /* Model 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 3.xcdatamodel"; sourceTree = ""; }; + D9EA98BBBC407AEAC1BC94DA /* Model 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 2.xcdatamodel"; sourceTree = ""; }; + D9EA98CB6D44FF6A0288FC89 /* FileManager+DKTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FileManager+DKTests.swift"; sourceTree = ""; }; + D9EA99A4330322758C784868 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; + D9EA99D5EB83BC0E9015C313 /* DKMigrationError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKMigrationError.swift; sourceTree = ""; }; + D9EA99E2FFD000179F191F13 /* AnotherTestModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AnotherTestModel.xcdatamodel; sourceTree = ""; }; + D9EA9A73D2D39D67BBAF51C8 /* AnotherTestModel 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AnotherTestModel 2.xcdatamodel"; sourceTree = ""; }; + D9EA9AB0036EBA3C7F88DA04 /* DKProgressiveStoreLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKProgressiveStoreLoader.swift; sourceTree = ""; }; + D9EA9AEF2F0376F0FA6D8577 /* DKModelsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKModelsTests.swift; sourceTree = ""; }; + D9EA9BDD0BBD54DE9E24505B /* TestModelV2ToV3Mapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestModelV2ToV3Mapping.swift; sourceTree = ""; }; + D9EA9E28AE0E92A2032A17C6 /* DKStoreLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DKStoreLoader.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -139,6 +195,7 @@ 9461A3C41CD7D9FE008EEC3C /* CoreData */, 9461A3BF1CD7D365008EEC3C /* Helpers */, 9461A3BA1CD7D2DA008EEC3C /* Managed */, + D9EA948CD1F385051BEAC919 /* Migration */, ); path = Classes; sourceTree = ""; @@ -232,6 +289,7 @@ 94F74A9D1CD52E9C000F1F4D /* CoreData */, 94F74A9C1CD529C5000F1F4D /* Helpers */, 94F74A931CD517C8000F1F4D /* Contracts */, + D9EA9782591E356D63B69097 /* Migration */, ); path = Classes; sourceTree = ""; @@ -244,6 +302,7 @@ 94F74A941CD524CF000F1F4D /* Storage.swift */, 94F74A961CD525FC000F1F4D /* Context.swift */, 94F74A981CD52627000F1F4D /* Entity.swift */, + D9EA9E28AE0E92A2032A17C6 /* DKStoreLoader.swift */, ); path = Contracts; sourceTree = ""; @@ -262,6 +321,7 @@ children = ( 94F74AA01CD52F2B000F1F4D /* Extensions */, 94F74A9E1CD52EB5000F1F4D /* CoreDataLocalStorage.swift */, + D9EA936F8C0B61D5936A229F /* DKStandardStoreLoader.swift */, ); path = CoreData; sourceTree = ""; @@ -294,6 +354,39 @@ path = Errors; sourceTree = ""; }; + D9EA948CD1F385051BEAC919 /* Migration */ = { + isa = PBXGroup; + children = ( + D9EA9E58C1FF815624DBA8F2 /* Model.xcdatamodeld */, + D9EA92D2C53985DAF69565A0 /* TestData.swift */, + D9EA9BDD0BBD54DE9E24505B /* TestModelV2ToV3Mapping.swift */, + D9EA96EBD15390FD9BCD9D03 /* DKProgressiveStoreLoaderTests.swift */, + D9EA9AEF2F0376F0FA6D8577 /* DKModelsTests.swift */, + D9EA9C7AEEC132E986272C8E /* AnotherTestModel.xcdatamodeld */, + D9EA94286DF73003A70255BB /* DKMigrationTests.swift */, + D9EA917D1C5413697A29EFC3 /* DKStoreFileTests.swift */, + D9EA98CB6D44FF6A0288FC89 /* FileManager+DKTests.swift */, + D9EA93B04BA179457F7FB22E /* DKVersionFromFileNamePolicyTests.swift */, + 8AFF0ADF1EDC32E8004A8714 /* AnotherModelMapping.xcmappingmodel */, + 8AFF0AE21EDC344F004A8714 /* ModelMapping.xcmappingmodel */, + ); + path = Migration; + sourceTree = ""; + }; + D9EA9782591E356D63B69097 /* Migration */ = { + isa = PBXGroup; + children = ( + D9EA99D5EB83BC0E9015C313 /* DKMigrationError.swift */, + D9EA9AB0036EBA3C7F88DA04 /* DKProgressiveStoreLoader.swift */, + D9EA969BF1FDB0C4812FB0C6 /* DKModels.swift */, + D9EA9346FEF8FCB055D4780C /* DKMigration.swift */, + D9EA925DB1761FEA5B969535 /* DKStoreFile.swift */, + D9EA9812AEBFAC3BA2A088E0 /* DKVersionPolicy.swift */, + D9EA90C90848DB05D6FF4B78 /* DKMigrationFactory.swift */, + ); + path = Migration; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -456,8 +549,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8AFF0AE51EDD6ED6004A8714 /* DKProgressiveStoreLoader.swift in Sources */, 94BB39161CD9B9B0001A2DBE /* StoreRef.swift in Sources */, + 8AFF0AEA1EDD6ED6004A8714 /* DKMigrationFactory.swift in Sources */, 94BB391A1CD9B9BB001A2DBE /* Storage.swift in Sources */, + 8AFF0AE81EDD6ED6004A8714 /* DKStoreFile.swift in Sources */, + 8AFF0AE41EDD6ED6004A8714 /* DKMigrationError.swift in Sources */, 94BB39191CD9B9B8001A2DBE /* ContextRef.swift in Sources */, 94BB39101CD9B998001A2DBE /* NSManagedObjectContext.swift in Sources */, 94BB39131CD9B9A5001A2DBE /* Request.swift in Sources */, @@ -466,9 +563,14 @@ 94BB39111CD9B99B001A2DBE /* NSManagedObject.swift in Sources */, 94BB39141CD9B9A7001A2DBE /* FileUtils.swift in Sources */, 94BB391B1CD9B9BE001A2DBE /* Context.swift in Sources */, + 8AFF0AE91EDD6ED6004A8714 /* DKVersionPolicy.swift in Sources */, + 8AFF0AE71EDD6ED6004A8714 /* DKMigration.swift in Sources */, 94BB391C1CD9B9C1001A2DBE /* Entity.swift in Sources */, + 8AFF0AE61EDD6ED6004A8714 /* DKModels.swift in Sources */, 94BB39181CD9B9B5001A2DBE /* OptionRef.swift in Sources */, 94BB39171CD9B9B3001A2DBE /* ModelRef.swift in Sources */, + D9EA961A6EC506D5015D53DC /* DKStoreLoader.swift in Sources */, + D9EA9E7F6E3C8C1C85BFE675 /* DKStandardStoreLoader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -479,6 +581,7 @@ 94F74A9F1CD52EB5000F1F4D /* CoreDataLocalStorage.swift in Sources */, 94F74A781CD51764000F1F4D /* DataKernel.xcdatamodeld in Sources */, 94F74AA91CD53261000F1F4D /* ModelRef.swift in Sources */, + 8AFF0AE11EDC3392004A8714 /* AnotherModelMapping.xcmappingmodel in Sources */, 94F74A951CD524CF000F1F4D /* Storage.swift in Sources */, 94F74AB21CD53F04000F1F4D /* NSManagedObjectContext.swift in Sources */, 9461A3B01CD7AA19008EEC3C /* NSManagedObject.swift in Sources */, @@ -492,6 +595,16 @@ 94F74A971CD525FC000F1F4D /* Context.swift in Sources */, 94F74A701CD51764000F1F4D /* AppDelegate.swift in Sources */, 94F74AA41CD5303E000F1F4D /* FileUtils.swift in Sources */, + D9EA9C35411474B1E4C90D74 /* DKMigrationError.swift in Sources */, + D9EA9E3FFE6B2CED9CCD50A3 /* DKProgressiveStoreLoader.swift in Sources */, + D9EA9435C6D9E1A95035EB40 /* DKModels.swift in Sources */, + D9EA9B3FD126D79F83711B44 /* DKMigration.swift in Sources */, + D9EA9B02719AA217097EF74B /* DKStoreFile.swift in Sources */, + D9EA959ED577EB336DA4CD6C /* DKVersionPolicy.swift in Sources */, + D9EA9B0B0E741649473CD8B3 /* DKMigrationFactory.swift in Sources */, + D9EA93F718F4B1FE60ED9ADA /* Model.xcdatamodeld in Sources */, + D9EA9C1CFB6F5BC5857D7FB5 /* DKStoreLoader.swift in Sources */, + D9EA9A0FBEDF3BE12C97AA15 /* DKStandardStoreLoader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -501,11 +614,23 @@ files = ( 945D44A71CD9012E00A47743 /* CoreDataContextTests.swift in Sources */, 94F74A881CD51765000F1F4D /* DataKernelTests.swift in Sources */, + 8AFF0AE31EDC344F004A8714 /* ModelMapping.xcmappingmodel in Sources */, 9461A3C11CD7D37D008EEC3C /* FileUtilsTests.swift in Sources */, + 8AFF0AD41EDC30B9004A8714 /* TestModelV2ToV3Mapping.swift in Sources */, 9461A3BD1CD7D2DA008EEC3C /* Car+CoreDataProperties.swift in Sources */, + 8AFF0ADB1EDC30CC004A8714 /* DKVersionFromFileNamePolicyTests.swift in Sources */, + 8AFF0AD51EDC30B9004A8714 /* DKProgressiveStoreLoaderTests.swift in Sources */, 9461A3C31CD7D632008EEC3C /* RequestTests.swift in Sources */, + 8AFF0AD21EDC30AE004A8714 /* Model.xcdatamodeld in Sources */, 9461A3BE1CD7D2DA008EEC3C /* Car.swift in Sources */, + 8AFF0AD31EDC30B9004A8714 /* TestData.swift in Sources */, + 8AFF0AE01EDC32E8004A8714 /* AnotherModelMapping.xcmappingmodel in Sources */, + 8AFF0AD71EDC30BF004A8714 /* DKMigrationTests.swift in Sources */, + 8AFF0AD91EDC30BF004A8714 /* FileManager+DKTests.swift in Sources */, + 8AFF0AD61EDC30B9004A8714 /* DKModelsTests.swift in Sources */, 9461A3B41CD7D1A0008EEC3C /* DataModel.xcdatamodeld in Sources */, + 8AFF0AD81EDC30BF004A8714 /* DKStoreFileTests.swift in Sources */, + 8AFF0ADA1EDC30C4004A8714 /* AnotherTestModel.xcdatamodeld in Sources */, 9461A3C61CD7DA15008EEC3C /* CoreDataLocalStoreTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -781,6 +906,29 @@ sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; + D9EA9C7AEEC132E986272C8E /* AnotherTestModel.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + D9EA9A73D2D39D67BBAF51C8 /* AnotherTestModel 2.xcdatamodel */, + D9EA99E2FFD000179F191F13 /* AnotherTestModel.xcdatamodel */, + ); + currentVersion = D9EA9A73D2D39D67BBAF51C8 /* AnotherTestModel 2.xcdatamodel */; + path = AnotherTestModel.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; + D9EA9E58C1FF815624DBA8F2 /* Model.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + D9EA98BBBC407AEAC1BC94DA /* Model 2.xcdatamodel */, + D9EA981897B6D9F2E55419F3 /* Model 3.xcdatamodel */, + D9EA99A4330322758C784868 /* Model.xcdatamodel */, + ); + currentVersion = D9EA981897B6D9F2E55419F3 /* Model 3.xcdatamodel */; + path = Model.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; /* End XCVersionGroup section */ }; rootObject = 94F74A641CD51764000F1F4D /* Project object */; diff --git a/DataKernel/Classes/Contracts/DKStoreLoader.swift b/DataKernel/Classes/Contracts/DKStoreLoader.swift new file mode 100644 index 0000000..1f72973 --- /dev/null +++ b/DataKernel/Classes/Contracts/DKStoreLoader.swift @@ -0,0 +1,6 @@ +import Foundation +import CoreData + +public protocol DKStoreLoader { + func append(store: URL, ofType: String, to coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore +} diff --git a/DataKernel/Classes/CoreData/CoreDataLocalStorage.swift b/DataKernel/Classes/CoreData/CoreDataLocalStorage.swift index e2774bf..a962456 100644 --- a/DataKernel/Classes/CoreData/CoreDataLocalStorage.swift +++ b/DataKernel/Classes/CoreData/CoreDataLocalStorage.swift @@ -14,8 +14,7 @@ open class CoreDataLocalStorage: Storage { // MARK: - Storage internal let store: StoreRef - internal let migration: Bool - + internal let loader: DKStoreLoader open var uiContext: Context! open func perform(_ ephemeral: Bool, unitOfWork: @escaping (_ context: Context, _ save: () -> Void) throws -> Void) throws { @@ -92,7 +91,7 @@ open class CoreDataLocalStorage: Storage { } open func restoreStore() throws { - self.persistentStore = try initializeStore(store, coordinator: self.persistentStoreCoordinator, migrate: self.migration) + self.persistentStore = try initializeStore(store, coordinator: self.persistentStoreCoordinator) } // MARK: - Props @@ -103,14 +102,16 @@ open class CoreDataLocalStorage: Storage { internal var rootContext: NSManagedObjectContext! = nil // MARK: - Init - - public init(store: StoreRef, model: ModelRef, migration: Bool) throws { + public convenience init(store: StoreRef, model: ModelRef, migration: Bool) throws { + try self.init(store: store, model: model, loader: DKStandardStoreLoader(migrate: migration)) + } + + public init(store: StoreRef, model: ModelRef, loader: DKStoreLoader) throws { self.store = store - self.migration = migration - + self.loader = loader self.model = model.build()! self.persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.model) - self.persistentStore = try initializeStore(store, coordinator: self.persistentStoreCoordinator, migrate: migration) + self.persistentStore = try initializeStore(store, coordinator: self.persistentStoreCoordinator) self.rootContext = initializeContext(.coordinator(self.persistentStoreCoordinator), concurrency: .privateQueueConcurrencyType) self.uiContext = initializeContext(.context(self.rootContext), concurrency: .mainQueueConcurrencyType) } @@ -155,10 +156,9 @@ open class CoreDataLocalStorage: Storage { return context } - fileprivate func initializeStore(_ store: StoreRef, coordinator: NSPersistentStoreCoordinator, migrate: Bool) throws -> NSPersistentStore { + fileprivate func initializeStore(_ store: StoreRef, coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore { try checkStorePath(store) - let options = migrate ? OptionRef.migration : OptionRef.default - return try addStore(store, coordinator: coordinator, options: options.build()) + return try addStore(store, coordinator: coordinator) } fileprivate func checkStorePath(_ store: StoreRef) throws { @@ -166,13 +166,13 @@ open class CoreDataLocalStorage: Storage { try FileManager.default.createDirectory(at: path, withIntermediateDirectories: true, attributes: nil) } - fileprivate func addStore(_ store: StoreRef, coordinator: NSPersistentStoreCoordinator, options: [AnyHashable: Any], retry: Bool = true) throws -> NSPersistentStore { + fileprivate func addStore(_ store: StoreRef, coordinator: NSPersistentStoreCoordinator, retry: Bool = true) throws -> NSPersistentStore { var pstore: NSPersistentStore? var error: NSError? - + let loader = self.loader coordinator.performAndWait({ do { - pstore = try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: store.location() as URL, options: options) + pstore = try loader.append(store: store.location() as URL, ofType: NSSQLiteStoreType, to: coordinator) } catch let _error as NSError { error = _error } @@ -182,7 +182,7 @@ open class CoreDataLocalStorage: Storage { let errorOnMigration = error.code == NSPersistentStoreIncompatibleVersionHashError || error.code == NSMigrationMissingSourceModelError if errorOnMigration && retry { try cleanStoreOnFailedMigration(store) - return try addStore(store, coordinator: coordinator, options: options, retry: false) + return try addStore(store, coordinator: coordinator, retry: false) } else { throw error } @@ -201,6 +201,4 @@ open class CoreDataLocalStorage: Storage { try FileManager.default.removeItem(at: shmSidecar) try FileManager.default.removeItem(at: walSidecar) } - - } diff --git a/DataKernel/Classes/CoreData/DKStandardStoreLoader.swift b/DataKernel/Classes/CoreData/DKStandardStoreLoader.swift new file mode 100644 index 0000000..83af865 --- /dev/null +++ b/DataKernel/Classes/CoreData/DKStandardStoreLoader.swift @@ -0,0 +1,14 @@ +import Foundation +import CoreData + +public struct DKStandardStoreLoader: DKStoreLoader { + public let migrate: Bool + public func append(store: URL, ofType: String, to coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore { + let options = migrate ? OptionRef.migration : OptionRef.default + return try coordinator.addPersistentStore( + ofType: ofType, + configurationName: nil, + at: store, + options: options.build()) + } +} diff --git a/DataKernel/Classes/Migration/DKMigration.swift b/DataKernel/Classes/Migration/DKMigration.swift new file mode 100644 index 0000000..7f2c090 --- /dev/null +++ b/DataKernel/Classes/Migration/DKMigration.swift @@ -0,0 +1,61 @@ +import Foundation +import CoreData + +public struct DKMigration { + public let from: NSManagedObjectModel + public let to: NSManagedObjectModel + public let mapping: NSMappingModel? + + public func apply(url: URL, sourceStoreType: String, targetStoreType: String) throws { + if self.canPerformLightweightMigration(sourceStoreType: sourceStoreType, targetStoreType: targetStoreType) { + try performLightweightMigration(url: url, storeType: sourceStoreType) + } else { + try performFullMigration(url: url, sourceStoreType: sourceStoreType, targetStoreType: targetStoreType) + } + } + + public func canPerformLightweightMigration(sourceStoreType: String, targetStoreType: String) -> Bool { + if sourceStoreType != targetStoreType { + return false + } + guard let mapping = mapping else { + return true + } + for entityMapping in mapping.entityMappings { + if entityMapping.mappingType == .customEntityMappingType { + return false + } + } + return true + } + + public func performLightweightMigration(url: URL, storeType: String) throws { + let options: [AnyHashable: Any] = [ + NSMigratePersistentStoresAutomaticallyOption: true, + NSInferMappingModelAutomaticallyOption: mapping == nil + ] + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: to) + try coordinator.addPersistentStore( + ofType: storeType, + configurationName: nil, + at: url, + options: options) + } + + public func performFullMigration(url: URL, sourceStoreType: String, targetStoreType: String) throws { + let tempDir = URL(fileURLWithPath: NSTemporaryDirectory()) + let tempUrl = tempDir.appendingPathComponent("\(UUID().uuidString).tmp") + let migrationManager = NSMigrationManager( + sourceModel: from, + destinationModel: to) + try! migrationManager.migrateStore( + from: url, + sourceType: sourceStoreType, + options: nil, + with: mapping, + toDestinationURL: tempUrl, + destinationType: targetStoreType, + destinationOptions: nil) + try DKStoreFile(url: tempUrl).move(to: url) + } +} diff --git a/DataKernel/Classes/Migration/DKMigrationError.swift b/DataKernel/Classes/Migration/DKMigrationError.swift new file mode 100644 index 0000000..bc938f4 --- /dev/null +++ b/DataKernel/Classes/Migration/DKMigrationError.swift @@ -0,0 +1,7 @@ +import Foundation + +public enum DKMigrationError: Error { + case failedToGetVersionFromFilename(filename: String) + case failedToCreateModel(file: String?) + case failedToBuildMigrationPath +} diff --git a/DataKernel/Classes/Migration/DKMigrationFactory.swift b/DataKernel/Classes/Migration/DKMigrationFactory.swift new file mode 100644 index 0000000..aac675f --- /dev/null +++ b/DataKernel/Classes/Migration/DKMigrationFactory.swift @@ -0,0 +1,43 @@ +import Foundation +import CoreData + +public struct DKMigrationFactory { + public let models: DKModels + public let versionPolicy: DKVersionPolicy + + public func migrations(from: NSManagedObjectModel, to: NSManagedObjectModel) throws -> [DKMigration] { + let modelFiles = try sortedModelFilesByVersionDesc() + var migrations = [DKMigration]() + var targetModel: NSManagedObjectModel? = nil + for modelFile in modelFiles { + guard let sourceModel = NSManagedObjectModel(contentsOf: URL(fileURLWithPath: modelFile)) else { + throw DKMigrationError.failedToCreateModel(file: modelFile) + } + if let target = targetModel { + let mapping = NSMappingModel(from: [models.bundle], forSourceModel: sourceModel, destinationModel: target) + let migration = DKMigration(from: sourceModel, to: target, mapping: mapping) + migrations.append(migration) + if sourceModel == from { + return migrations.reversed() + } + targetModel = sourceModel + } else { + if sourceModel == to { + targetModel = sourceModel + } + } + } + throw DKMigrationError.failedToBuildMigrationPath + } + + private func sortedModelFilesByVersionDesc() throws -> [String] { + let fileAndVersion = try models.files.flatMap { (file: String) -> (file: String, version: Int)? in + let modelFile = URL(fileURLWithPath: file) + let version = try self.versionPolicy.version(modelUrl: modelFile) + return (file: file, version: version) + } + return fileAndVersion + .sorted(by: { $0.version > $1.version }) + .map { $0.file } + } +} diff --git a/DataKernel/Classes/Migration/DKModels.swift b/DataKernel/Classes/Migration/DKModels.swift new file mode 100644 index 0000000..0cb263e --- /dev/null +++ b/DataKernel/Classes/Migration/DKModels.swift @@ -0,0 +1,48 @@ +import Foundation +import CoreData + +public struct DKModels { + public let name: String + public let bundle: Bundle + + public init(name: String, bundle: Bundle? = nil) { + self.name = name + self.bundle = bundle ?? Bundle.main + } + + public var files: [String] { + if let modelDir = self.bundle.path(forResource: self.name, ofType: "momd") { + let modelDirName = NSURL(fileURLWithPath: modelDir, isDirectory: true).lastPathComponent + return self.bundle.paths(forResourcesOfType: "mom", inDirectory: modelDirName) + } + return [] + } + + public func modelFile(name: String) -> String { + guard let file = files.first(where: { + name == NSURL(fileURLWithPath: $0, isDirectory: false).deletingPathExtension?.lastPathComponent + }) else { + fatalError("Managed Model not found for version \(name)") + } + return file + } + + public func model(name: String) -> NSManagedObjectModel { + let file = modelFile(name: name) + guard let managedModel = NSManagedObjectModel(contentsOf: URL(fileURLWithPath: file)) else { + fatalError("Unable to create NSManagedObjectModel for \(name), file = \(file)") + } + return managedModel + } + + public func currentModel() throws -> NSManagedObjectModel { + guard let modelPath = self.bundle.path(forResource: self.name, ofType: "momd") else { + throw DKMigrationError.failedToCreateModel(file: nil) + } + if let model = NSManagedObjectModel(contentsOf: URL(fileURLWithPath: modelPath)) { + return model + } else { + throw DKMigrationError.failedToCreateModel(file: modelPath) + } + } +} diff --git a/DataKernel/Classes/Migration/DKProgressiveStoreLoader.swift b/DataKernel/Classes/Migration/DKProgressiveStoreLoader.swift new file mode 100644 index 0000000..b253329 --- /dev/null +++ b/DataKernel/Classes/Migration/DKProgressiveStoreLoader.swift @@ -0,0 +1,42 @@ +import Foundation +import CoreData + +public class DKProgressiveStoreLoader: DKStoreLoader { + public let models: DKModels + public let migrationFactory: DKMigrationFactory + + public init(models: DKModels, versionPolicy: DKVersionPolicy? = nil) { + self.models = models + let versionPolicy = versionPolicy ?? DKVersionFromFileNamePolicy(prefix: models.name) + self.migrationFactory = DKMigrationFactory(models: models, versionPolicy: versionPolicy) + } + + public func append(store: URL, ofType: String, to coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore { + let fileExists = FileManager.default.fileExists(atPath: store.path) + if fileExists { + try migrateIfNeeded(store: store, ofType: ofType, targetModel: coordinator.managedObjectModel) + } + let options: [AnyHashable: Any] = [ + NSSQLitePragmasOption: [ + "journal_mode": "WAL" + ] + ] + return try coordinator.addPersistentStore( + ofType: ofType, + configurationName: nil, + at: store, + options: options) + } + + private func migrateIfNeeded(store: URL, ofType type: String, targetModel: NSManagedObjectModel) throws { + let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: type, at: store, options: nil) + if targetModel.isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata) { + return + } + let storeModel = NSManagedObjectModel.mergedModel(from: [models.bundle], forStoreMetadata: metadata)! + let migrations = try migrationFactory.migrations(from: storeModel, to: targetModel) + for migration in migrations { + try migration.apply(url: store, sourceStoreType: type, targetStoreType: type) + } + } +} diff --git a/DataKernel/Classes/Migration/DKStoreFile.swift b/DataKernel/Classes/Migration/DKStoreFile.swift new file mode 100644 index 0000000..b7d203b --- /dev/null +++ b/DataKernel/Classes/Migration/DKStoreFile.swift @@ -0,0 +1,31 @@ +import Foundation + +public struct DKStoreFile { + public let url: URL + + public func remove() throws { + let fileManager = FileManager.default + for path in paths() { + if fileManager.fileExists(atPath: path) { + try fileManager.removeItem(atPath: path) + } + } + } + + public func move(to: URL) throws { + try DKStoreFile(url: to).remove() + let fileManager = FileManager.default + let sourcePaths = paths() + let targetPaths = paths(from: to) + for i in 0.. [String] { + let path = (url ?? self.url).path + return [path, path + "-shm", path + "-wal"] + } +} diff --git a/DataKernel/Classes/Migration/DKVersionPolicy.swift b/DataKernel/Classes/Migration/DKVersionPolicy.swift new file mode 100644 index 0000000..38c1a16 --- /dev/null +++ b/DataKernel/Classes/Migration/DKVersionPolicy.swift @@ -0,0 +1,17 @@ +import Foundation + +public protocol DKVersionPolicy { + func version(modelUrl: URL) throws -> Int +} + +public struct DKVersionFromFileNamePolicy: DKVersionPolicy { + public let prefix: String + public func version(modelUrl: URL) throws -> Int { + let filename = modelUrl.deletingPathExtension().lastPathComponent + if filename.hasPrefix(prefix) { + let suffix = filename.substring(from: filename.index(filename.startIndex, offsetBy: prefix.characters.count)) + return Int(suffix.trimmingCharacters(in: CharacterSet.whitespaces)) ?? 1 + } + throw DKMigrationError.failedToGetVersionFromFilename(filename: filename) + } +} diff --git a/DataKernelTests/Classes/Migration/AnotherModelMapping.xcmappingmodel/xcmapping.xml b/DataKernelTests/Classes/Migration/AnotherModelMapping.xcmappingmodel/xcmapping.xml new file mode 100644 index 0000000..0001b6c --- /dev/null +++ b/DataKernelTests/Classes/Migration/AnotherModelMapping.xcmappingmodel/xcmapping.xml @@ -0,0 +1,83 @@ + + + + + + 134481920 + CEA5632C-F0A1-4496-8592-4D0CDBFDB114 + 106 + + + + NSPersistenceFrameworkVersion + 754 + NSStoreModelVersionHashes + + XDDevAttributeMapping + + 0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc= + + XDDevEntityMapping + + qeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI= + + XDDevMappingModel + + EqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ= + + XDDevPropertyMapping + + XN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA= + + XDDevRelationshipMapping + + akYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs= + + + NSStoreModelVersionHashesVersion + 3 + NSStoreModelVersionIdentifiers + + + + + + + + + FioEntity + Undefined + 1 + FioEntity + 1 + + + + + + lastName + + + + firstName + + + + DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGBCkEKlgkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRv +cBIAAYagrxB8AAcACAAXADMANAA1AD0APgBZAFoAWwBhAGIAbgCEAIUAhgCHAIgAiQCKAIsAjACNAKYAqQCwALYAxQDUANcA5gD1APgAWAEIARcBGwEfAS4BNAE1AT0BTAFVAV0BXgFfAXQBdQF9AX4BfwGLAZ8BoAGhAaIBowGkAaUBpgGnAbYBxQHUAdgB5wH2AfcCBgIVAiQCMAJCAkMCRAJFAkYCRwJIAkkCWAJnAnYChQKGApUCpAKzArsC0ALRAtkC5QL5AwgDFwMmAyoDOQNIA1cDZgN1A4EDkwOiA7EDwAPPA94D7QP8A/0EAAQJBA0EEQQVBB0EIAQkBCVVJG51bGzXAAkACgALAAwADQAOAA8AEAARABIAEwAUABMAFl8QD194ZF9yb290UGFja2FnZVYkY2xhc3NcX3hkX2NvbW1lbnRzXxAQX3hkX21vZGVsTWFuYWdlcl8QFV9jb25maWd1cmF0aW9uc0J5TmFtZV1feGRfbW9kZWxOYW1lXxAXX21vZGVsVmVyc2lvbklkZW50aWZpZXKAAoB7gHiAAIB5gACAet4AGAAZABoAGwAcAB0AHgAKAB8AIAAhACIAIwAkACUAJgAnACgAJQATACsALAAtAC4ALwAlACUAE18QHFhEQnVja2V0Rm9yQ2xhc3Nlc3dhc0VuY29kZWRfEBpYREJ1Y2tldEZvclBhY2thZ2Vzc3RvcmFnZV8QHFhEQnVja2V0Rm9ySW50ZXJmYWNlc3N0b3JhZ2VfEA9feGRfb3duaW5nTW9kZWxfEB1YREJ1Y2tldEZvclBhY2thZ2Vzd2FzRW5jb2RlZFZfb3duZXJfEBtYREJ1Y2tldEZvckRhdGFUeXBlc3N0b3JhZ2VbX3Zpc2liaWxpdHlfEBlYREJ1Y2tldEZvckNsYXNzZXNzdG9yYWdlVV9uYW1lXxAfWERCdWNrZXRGb3JJbnRlcmZhY2Vzd2FzRW5jb2RlZF8QHlhEQnVja2V0Rm9yRGF0YVR5cGVzd2FzRW5jb2RlZF8QEF91bmlxdWVFbGVtZW50SUSABIB2gHSAAYAEgACAdYB3EACABYADgASABIAAUFNZRVPTADYANwAKADgAOgA8V05TLmtleXNaTlMub2JqZWN0c6EAOYAGoQA7gAeAJVlGaW9FbnRpdHnfEBAAPwBAAEEAQgAdAEMARAAfAEUARgAKACEARwBIACQASQBKAEsAJQAlABAATwBQAC0AJQBKAFMAOQBKAFYAVwBYXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zZHVwbGljYXRlc18QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNzdG9yYWdlW19pc0Fic3RyYWN0gAmALIAEgASAAoAKgHGABIAJgHOABoAJgHKACAgSacTmSldvcmRlcmVk0wA2ADcACgBcAF4APKEAXYALoQBfgAyAJV5YRF9QU3RlcmVvdHlwZdkAHQAhAGMACgAkAGQAHwBJAGUAOwBdAEoAaQATACUALQBYAG1fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAB4ALgAmAK4AAgAQIgA3TADYANwAKAG8AeQA8qQBwAHEAcgBzAHQAdQB2AHcAeIAOgA+AEIARgBKAE4AUgBWAFqkAegB7AHwAfQB+AH8AgACBAIKAF4AbgByAHoAfgCGAI4AmgCqAJV8QE1hEUE1Db21wb3VuZEluZGV4ZXNfEBBYRF9QU0tfZWxlbWVudElEXxAZWERQTVVuaXF1ZW5lc3NDb25zdHJhaW50c18QGlhEX1BTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAZWERfUFNLX2ZldGNoUmVxdWVzdHNBcnJheV8QEVhEX1BTS19pc0Fic3RyYWN0XxAPWERfUFNLX3VzZXJJbmZvXxATWERfUFNLX2NsYXNzTWFwcGluZ18QFlhEX1BTS19lbnRpdHlDbGFzc05hbWXfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwCZABMAXwBYAFgAWAAtAFgAoABwAFgAWAATAFhVX3R5cGVYX2RlZmF1bHRcX2Fzc29jaWF0aW9uW19pc1JlYWRPbmx5WV9pc1N0YXRpY1lfaXNVbmlxdWVaX2lzRGVyaXZlZFpfaXNPcmRlcmVkXF9pc0NvbXBvc2l0ZVdfaXNMZWFmgACAGIAAgAwICAgIgBqADggIgAAI0gA3AAoApwCooIAZ0gCqAKsArACtWiRjbGFzc25hbWVYJGNsYXNzZXNeTlNNdXRhYmxlQXJyYXmjAKwArgCvV05TQXJyYXlYTlNPYmplY3TSAKoAqwCxALJfEBBYRFVNTFByb3BlcnR5SW1wpACzALQAtQCvXxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAEwATAF8AWABYAFgALQBYAKAAcQBYAFgAEwBYgACAAIAAgAwICAgIgBqADwgIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAxwATAF8AWABYAFgALQBYAKAAcgBYAFgAEwBYgACAHYAAgAwICAgIgBqAEAgIgAAI0gA3AAoA1QCooIAZ3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAEwATAF8AWABYAFgALQBYAKAAcwBYAFgAEwBYgACAAIAAgAwICAgIgBqAEQgIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMA6AATAF8AWABYAFgALQBYAKAAdABYAFgAEwBYgACAIIAAgAwICAgIgBqAEggIgAAI0gA3AAoA9gCooIAZ3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMA+gATAF8AWABYAFgALQBYAKAAdQBYAFgAEwBYgACAIoAAgAwICAgIgBqAEwgIgAAICN8QDwCOAI8AkAAdAJEAkgCTAB8AlAAKACEAlQCWACQAlwATAQoAEwBfAFgAWABYAC0AWACgAHYAWABYABMAWIAAgCSAAIAMCAgICIAagBQICIAACNMANgA3AAoBGAEZADygoIAl0gCqAKsBHAEdXxATTlNNdXRhYmxlRGljdGlvbmFyeaMBHAEeAK9cTlNEaWN0aW9uYXJ53xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMBIQATAF8AWABYAFgALQBYAKAAdwBYAFgAEwBYgACAJ4AAgAwICAgIgBqAFQgIgAAI1gAhAAoAJABJAB0AHwEvATAAEwBYABMALYAogCmAAAiAAF8QFFhER2VuZXJpY1JlY29yZENsYXNz0gCqAKsBNgE3XVhEVU1MQ2xhc3NJbXCmATgBOQE6ATsBPACvXVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAOQATAF8AWABYAFgALQBYAKAAeABYAFgAEwBYgACABoAAgAwICAgIgBqAFggIgAAI0gCqAKsBTQFOXxASWERVTUxTdGVyZW90eXBlSW1wpwFPAVABUQFSAVMBVACvXxASWERVTUxTdGVyZW90eXBlSW1wXVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA2ADcACgFWAVkAPKIBVwFYgC2ALqIBWgFbgC+AWoAlWWZpcnN0TmFtZVhsYXN0TmFtZd8QEgCOAI8AkAFgAB0AkgCTAWEAHwCRAWIAlAAKACEAlQCWACQAlwATABMAEwAlADsAWABYAWoALQBYAEoAWAFuAVcAWABYAXIAWF8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIAxCIAJCIBZgC0ICIAwCBIjYQN30wA2ADcACgF2AXkAPKIBdwF4gDKAM6IBegF7gDSASIAlXxASWERfUFByb3BTdGVyZW90eXBlXxASWERfUEF0dF9TdGVyZW90eXBl2QAdACEBgAAKACQBgQAfAEkBggFaAXcASgBpABMAJQAtAFgBil8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAvgDKACYArgACABAiANdMANgA3AAoBjAGVADyoAY0BjgGPAZABkQGSAZMBlIA2gDeAOIA5gDqAO4A8gD2oAZYBlwGYAZkBmgGbAZwBnYA+gD+AQIBCgEOARYBGgEeAJV8QG1hEX1BQU0tfaXNTdG9yZWRJblRydXRoRmlsZV8QG1hEX1BQU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QEFhEX1BQU0tfdXNlckluZm9fEBFYRF9QUFNLX2lzSW5kZXhlZF8QElhEX1BQU0tfaXNPcHRpb25hbF8QGlhEX1BQU0tfaXNTcG90bGlnaHRJbmRleGVkXxARWERfUFBTS19lbGVtZW50SURfEBNYRF9QUFNLX2lzVHJhbnNpZW503xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMA+gATAXoAWABYAFgALQBYAKABjQBYAFgAEwBYgACAIoAAgDQICAgIgBqANggIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAEwATAXoAWABYAFgALQBYAKABjgBYAFgAEwBYgACAAIAAgDQICAgIgBqANwgIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMBxwATAXoAWABYAFgALQBYAKABjwBYAFgAEwBYgACAQYAAgDQICAgIgBqAOAgIgAAI0wA2ADcACgHVAdYAPKCggCXfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwD6ABMBegBYAFgAWAAtAFgAoAGQAFgAWAATAFiAAIAigACANAgICAiAGoA5CAiAAAjfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwHpABMBegBYAFgAWAAtAFgAoAGRAFgAWAATAFiAAIBEgACANAgICAiAGoA6CAiAAAgJ3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMA+gATAXoAWABYAFgALQBYAKABkgBYAFgAEwBYgACAIoAAgDQICAgIgBqAOwgIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAEwATAXoAWABYAFgALQBYAKABkwBYAFgAEwBYgACAAIAAgDQICAgIgBqAPAgIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMA+gATAXoAWABYAFgALQBYAKABlABYAFgAEwBYgACAIoAAgDQICAgIgBqAPQgIgAAI2QAdACECJQAKACQCJgAfAEkCJwFaAXgASgBpABMAJQAtAFgCL18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAvgDOACYArgACABAiASdMANgA3AAoCMQI5ADynAjICMwI0AjUCNgI3AjiASoBLgEyATYBOgE+AUKcCOgI7AjwCPQI+Aj8CQIBRgFKAU4BUgFaAV4BYgCVfEB1YRF9QQXR0S19kZWZhdWx0VmFsdWVBc1N0cmluZ18QKFhEX1BBdHRLX2FsbG93c0V4dGVybmFsQmluYXJ5RGF0YVN0b3JhZ2VfEBdYRF9QQXR0S19taW5WYWx1ZVN0cmluZ18QFlhEX1BBdHRLX2F0dHJpYnV0ZVR5cGVfEBdYRF9QQXR0S19tYXhWYWx1ZVN0cmluZ18QHVhEX1BBdHRLX3ZhbHVlVHJhbnNmb3JtZXJOYW1lXxAgWERfUEF0dEtfcmVndWxhckV4cHJlc3Npb25TdHJpbmffEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwATABMBewBYAFgAWAAtAFgAoAIyAFgAWAATAFiAAIAAgACASAgICAiAGoBKCAiAAAjfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwD6ABMBewBYAFgAWAAtAFgAoAIzAFgAWAATAFiAAIAigACASAgICAiAGoBLCAiAAAjfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwATABMBewBYAFgAWAAtAFgAoAI0AFgAWAATAFiAAIAAgACASAgICAiAGoBMCAiAAAjfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwJ4ABMBewBYAFgAWAAtAFgAoAI1AFgAWAATAFiAAIBVgACASAgICAiAGoBNCAiAAAgRArzfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwATABMBewBYAFgAWAAtAFgAoAI2AFgAWAATAFiAAIAAgACASAgICAiAGoBOCAiAAAjfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwATABMBewBYAFgAWAAtAFgAoAI3AFgAWAATAFiAAIAAgACASAgICAiAGoBPCAiAAAjfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwATABMBewBYAFgAWAAtAFgAoAI4AFgAWAATAFiAAIAAgACASAgICAiAGoBQCAiAAAjSAKoAqwK0ArVdWERQTUF0dHJpYnV0ZaYCtgK3ArgCuQK6AK9dWERQTUF0dHJpYnV0ZVxYRFBNUHJvcGVydHlfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEBIAjgCPAJACvAAdAJIAkwK9AB8AkQK+AJQACgAhAJUAlgAkAJcAEwATABMAJQA7AFgAWALGAC0AWABKAFgBbgFYAFgAWALOAFhfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAXAiACQiAWYAuCAiAWwgSpTf1t9MANgA3AAoC0gLVADyiAXcBeIAygDOiAtYC14BdgGiAJdkAHQAhAtoACgAkAtsAHwBJAtwBWwF3AEoAaQATACUALQBYAuRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAWoAygAmAK4AAgAQIgF7TADYANwAKAuYC7wA8qAGNAY4BjwGQAZEBkgGTAZSANoA3gDiAOYA6gDuAPIA9qALwAvEC8gLzAvQC9QL2AveAX4BggGGAY4BkgGWAZoBngCXfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwD6ABMC1gBYAFgAWAAtAFgAoAGNAFgAWAATAFiAAIAigACAXQgICAiAGoA2CAiAAAjfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwATABMC1gBYAFgAWAAtAFgAoAGOAFgAWAATAFiAAIAAgACAXQgICAiAGoA3CAiAAAjfEA8AjgCPAJAAHQCRAJIAkwAfAJQACgAhAJUAlgAkAJcAEwMZABMC1gBYAFgAWAAtAFgAoAGPAFgAWAATAFiAAIBigACAXQgICAiAGoA4CAiAAAjTADYANwAKAycDKAA8oKCAJd8QDwCOAI8AkAAdAJEAkgCTAB8AlAAKACEAlQCWACQAlwATAPoAEwLWAFgAWABYAC0AWACgAZAAWABYABMAWIAAgCKAAIBdCAgICIAagDkICIAACN8QDwCOAI8AkAAdAJEAkgCTAB8AlAAKACEAlQCWACQAlwATAekAEwLWAFgAWABYAC0AWACgAZEAWABYABMAWIAAgESAAIBdCAgICIAagDoICIAACN8QDwCOAI8AkAAdAJEAkgCTAB8AlAAKACEAlQCWACQAlwATAPoAEwLWAFgAWABYAC0AWACgAZIAWABYABMAWIAAgCKAAIBdCAgICIAagDsICIAACN8QDwCOAI8AkAAdAJEAkgCTAB8AlAAKACEAlQCWACQAlwATABMAEwLWAFgAWABYAC0AWACgAZMAWABYABMAWIAAgACAAIBdCAgICIAagDwICIAACN8QDwCOAI8AkAAdAJEAkgCTAB8AlAAKACEAlQCWACQAlwATAPoAEwLWAFgAWABYAC0AWACgAZQAWABYABMAWIAAgCKAAIBdCAgICIAagD0ICIAACNkAHQAhA3YACgAkA3cAHwBJA3gBWwF4AEoAaQATACUALQBYA4BfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAWoAzgAmAK4AAgAQIgGnTADYANwAKA4IDigA8pwIyAjMCNAI1AjYCNwI4gEqAS4BMgE2AToBPgFCnA4sDjAONA44DjwOQA5GAaoBrgGyAbYBugG+AcIAl3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAEwATAtcAWABYAFgALQBYAKACMgBYAFgAEwBYgACAAIAAgGgICAgIgBqASggIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMA+gATAtcAWABYAFgALQBYAKACMwBYAFgAEwBYgACAIoAAgGgICAgIgBqASwgIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAEwATAtcAWABYAFgALQBYAKACNABYAFgAEwBYgACAAIAAgGgICAgIgBqATAgIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMCeAATAtcAWABYAFgALQBYAKACNQBYAFgAEwBYgACAVYAAgGgICAgIgBqATQgIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAEwATAtcAWABYAFgALQBYAKACNgBYAFgAEwBYgACAAIAAgGgICAgIgBqATggIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAEwATAtcAWABYAFgALQBYAKACNwBYAFgAEwBYgACAAIAAgGgICAgIgBqATwgIgAAI3xAPAI4AjwCQAB0AkQCSAJMAHwCUAAoAIQCVAJYAJACXABMAEwATAtcAWABYAFgALQBYAKACOABYAFgAEwBYgACAAIAAgGgICAgIgBqAUAgIgAAIWmR1cGxpY2F0ZXPSADcACgP+AKiggBnSAKoAqwQBBAJaWERQTUVudGl0eacEAwQEBAUEBgQHBAgAr1pYRFBNRW50aXR5XVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA2ADcACgQKBAsAPKCggCXTADYANwAKBA4EDwA8oKCAJdMANgA3AAoEEgQTADygoIAl0gCqAKsEFgQXXlhETW9kZWxQYWNrYWdlpgQYBBkEGgQbBBwAr15YRE1vZGVsUGFja2FnZV8QD1hEVU1MUGFja2FnZUltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDSADcACgQeAKiggBnTADYANwAKBCEEIgA8oKCAJVDSAKoAqwQmBCdZWERQTU1vZGVsowQmBCgAr1dYRE1vZGVsXxAPTlNLZXllZEFyY2hpdmVy0QQrAChUcm9vdIABAAgAGQAiACsANQA6AD8BOgFAAV0BbwF2AYMBlgGuAbwB1gHYAdoB3AHeAeAB4gHkAh0CPAJZAngCigKqArECzwLbAvcC/QMfA0ADUwNVA1cDWQNbA10DXwNhA2MDZQNnA2kDawNtA28DcAN0A4EDiQOUA5cDmQOcA54DoAOqA+0EEQQ1BFgEfwSfBMYE7QUNBTEFVQVhBWMFZQVnBWkFawVtBW8FcQVzBXUFdwV5BXsFfQV+BYMFiwWYBZsFnQWgBaIFpAWzBdgF/AYjBkcGSQZLBk0GTwZRBlMGVAZWBmMGdgZ4BnoGfAZ+BoAGggaEBoYGiAabBp0GnwahBqMGpQanBqkGqwatBq8GxQbYBvQHEQctB0EHUwdpB4IHwQfHB9AH3QfpB/MH/QgICBMIIAgoCCoILAguCDAIMQgyCDMINAg2CDgIOQg6CDwIPQhGCEcISQhSCF0IZgh1CHwIhAiNCJYIqQiyCMUI3AjuCS0JLwkxCTMJNQk2CTcJOAk5CTsJPQk+CT8JQQlCCYEJgwmFCYcJiQmKCYsJjAmNCY8JkQmSCZMJlQmWCZ8JoAmiCeEJ4wnlCecJ6QnqCesJ7AntCe8J8QnyCfMJ9Qn2CjUKNwo5CjsKPQo+Cj8KQApBCkMKRQpGCkcKSQpKClMKVApWCpUKlwqZCpsKnQqeCp8KoAqhCqMKpQqmCqcKqQqqCqsK6grsCu4K8AryCvMK9Ar1CvYK+Ar6CvsK/Ar+Cv8LDAsNCw4LEAsZCy8LNgtDC4ILhAuGC4gLiguLC4wLjQuOC5ALkguTC5QLlguXC7ALsgu0C7YLtwu5C9AL2QvnC/QMAgwXDCsMQgxUDJMMlQyXDJkMmwycDJ0MngyfDKEMowykDKUMpwyoDLEMxgzVDOoM+A0NDSENOA1KDVcNXA1eDWANZQ1nDWkNaw11DX4NyQ3sDgwOLA4uDjAOMg40DjYONw44DjoOOw49Dj4OQA5CDkMORA5GDkcOTA5ZDl4OYA5iDmcOaQ5rDm0Ogg6XDrwO4A8HDysPLQ8vDzEPMw81DzcPOA86D0cPWA9aD1wPXg9gD2IPZA9mD2gPeQ97D30Pfw+BD4MPhQ+HD4kPiw+pD8cP2g/uEAMQIBA0EEoQiRCLEI0QjxCREJIQkxCUEJUQlxCZEJoQmxCdEJ4Q3RDfEOEQ4xDlEOYQ5xDoEOkQ6xDtEO4Q7xDxEPIRMREzETURNxE5EToROxE8ET0RPxFBEUIRQxFFEUYRUxFUEVURVxGWEZgRmhGcEZ4RnxGgEaERohGkEaYRpxGoEaoRqxHqEewR7hHwEfIR8xH0EfUR9hH4EfoR+xH8Ef4R/xIAEj8SQRJDEkUSRxJIEkkSShJLEk0STxJQElESUxJUEpMSlRKXEpkSmxKcEp0SnhKfEqESoxKkEqUSpxKoEucS6RLrEu0S7xLwEvES8hLzEvUS9xL4EvkS+xL8EyETRRNsE5ATkhOUE5YTmBOaE5wTnROfE6wTuxO9E78TwRPDE8UTxxPJE9gT2hPcE94T4BPiE+QT5hPoFAgUMxRNFGYUgBSgFMMVAhUEFQYVCBUKFQsVDBUNFQ4VEBUSFRMVFBUWFRcVVhVYFVoVXBVeFV8VYBVhFWIVZBVmFWcVaBVqFWsVqhWsFa4VsBWyFbMVtBW1FbYVuBW6FbsVvBW+Fb8V/hYAFgIWBBYGFgcWCBYJFgoWDBYOFg8WEBYSFhMWFhZVFlcWWRZbFl0WXhZfFmAWYRZjFmUWZhZnFmkWahapFqsWrRavFrEWshazFrQWtRa3FrkWuha7Fr0Wvhb9Fv8XARcDFwUXBhcHFwgXCRcLFw0XDhcPFxEXEhcbFykXNhdEF1EXZBd7F40X2Bf7GBsYOxg9GD8YQRhDGEUYRhhHGEkYShhMGE0YTxhRGFIYUxhVGFYYWxhoGG0YbxhxGHYYeBh6GHwYoRjFGOwZEBkSGRQZFhkYGRoZHBkdGR8ZLBk9GT8ZQRlDGUUZRxlJGUsZTRleGWAZYhlkGWYZaBlqGWwZbhlwGa8ZsRmzGbUZtxm4GbkZuhm7Gb0ZvxnAGcEZwxnEGgMaBRoHGgkaCxoMGg0aDhoPGhEaExoUGhUaFxoYGlcaWRpbGl0aXxpgGmEaYhpjGmUaZxpoGmkaaxpsGnkaehp7Gn0avBq+GsAawhrEGsUaxhrHGsgayhrMGs0azhrQGtEbEBsSGxQbFhsYGxkbGhsbGxwbHhsgGyEbIhskGyUbZBtmG2gbahtsG20bbhtvG3Abcht0G3Ubdht4G3kbuBu6G7wbvhvAG8EbwhvDG8QbxhvIG8kbyhvMG80cDBwOHBAcEhwUHBUcFhwXHBgcGhwcHB0cHhwgHCEcRhxqHJEctRy3HLkcuxy9HL8cwRzCHMQc0RzgHOIc5BzmHOgc6hzsHO4c/Rz/HQEdAx0FHQcdCR0LHQ0dTB1OHVAdUh1UHVUdVh1XHVgdWh1cHV0dXh1gHWEdoB2iHaQdph2oHakdqh2rHawdrh2wHbEdsh20HbUd9B32Hfgd+h38Hf0d/h3/HgAeAh4EHgUeBh4IHgkeSB5KHkweTh5QHlEeUh5THlQeVh5YHlkeWh5cHl0enB6eHqAeoh6kHqUeph6nHqgeqh6sHq0erh6wHrEe8B7yHvQe9h74Hvke+h77Hvwe/h8AHwEfAh8EHwUfRB9GH0gfSh9MH00fTh9PH1AfUh9UH1UfVh9YH1kfZB9tH24fcB95H4Qfkx+eH6wfwR/VH+wf/iALIAwgDSAPIBwgHSAeICAgLSAuIC8gMSA6IEkgViBlIHcgiyCiILQgvSC+IMAgzSDOIM8g0SDSINsg5SDsIPQhBiELIRAAAAAAAAACAgAAAAAAAAQtAAAAAAAAAAAAAAAAAAAhEg== + + DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel 2.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGBW0FblgkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRv +cBIAAYagrxCUAAcACAAXADMANAA1AD0APgBZAFoAWwBhAGIAbgCEAIUAhgCHAIgAiQCKAIsAjACNAKYAqQCwALYAxQDUANcA5gD1APgAWAEIARcBGwEfAS4BNAE1AT0BTAFVAV8BYAFhAWIBdwF4AYABgQGCAY4BogGjAaQBpQGmAacBqAGpAaoBuQHIAdcB2wHqAfkB+gIJAhgCJwIzAkUCRgJHAkgCSQJKAksCTAJbAmoCeQKIAokCmAKnArYCvgLTAtQC3ALoAvwDCwMaAykDLQM8A0sDWgNpA3gDhAOWA6UDtAPDA9ID4QPwA/8EFAQVBB0EKQQ9BEwEWwRqBG4EfQSMBJsEqgS5BMUE1wTmBPUFBAUTBSIFMQVABUEFRAVNBVEFVQVZBWEFZAVoBWlVJG51bGzXAAkACgALAAwADQAOAA8AEAARABIAEwAUABMAFl8QD194ZF9yb290UGFja2FnZVYkY2xhc3NcX3hkX2NvbW1lbnRzXxAQX3hkX21vZGVsTWFuYWdlcl8QFV9jb25maWd1cmF0aW9uc0J5TmFtZV1feGRfbW9kZWxOYW1lXxAXX21vZGVsVmVyc2lvbklkZW50aWZpZXKAAoCTgJCAAICRgACAkt4AGAAZABoAGwAcAB0AHgAKAB8AIAAhACIAIwAkACUAJgAnACgAJQATACsALAAtAC4ALwAlACUAE18QHFhEQnVja2V0Rm9yQ2xhc3Nlc3dhc0VuY29kZWRfEBpYREJ1Y2tldEZvclBhY2thZ2Vzc3RvcmFnZV8QHFhEQnVja2V0Rm9ySW50ZXJmYWNlc3N0b3JhZ2VfEA9feGRfb3duaW5nTW9kZWxfEB1YREJ1Y2tldEZvclBhY2thZ2Vzd2FzRW5jb2RlZFZfb3duZXJfEBtYREJ1Y2tldEZvckRhdGFUeXBlc3N0b3JhZ2VbX3Zpc2liaWxpdHlfEBlYREJ1Y2tldEZvckNsYXNzZXNzdG9yYWdlVV9uYW1lXxAfWERCdWNrZXRGb3JJbnRlcmZhY2Vzd2FzRW5jb2RlZF8QHlhEQnVja2V0Rm9yRGF0YVR5cGVzd2FzRW5jb2RlZF8QEF91bmlxdWVFbGVtZW50SUSABICOgIyAAYAEgACAjYCPEACABYADgASABIAAUFNZRVPTADYANwAKADgAOgA8V05TLmtleXNaTlMub2JqZWN0c6EAOYAGoQA7gAeAJVlGaW9FbnRpdHnfEBAAPwBAAEEAQgAdAEMARAAfAEUARgAKACEARwBIACQASQBKAEsAJQAlABAATwBQAC0AJQBKAFMAOQBKAFYAVwBYXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zZHVwbGljYXRlc18QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNzdG9yYWdlW19pc0Fic3RyYWN0gAmALIAEgASAAoAKgImABIAJgIuABoAJgIqACAgSOpp3BldvcmRlcmVk0wA2ADcACgBcAF4APKEAXYALoQBfgAyAJV5YRF9QU3RlcmVvdHlwZdkAHQAhAGMACgAkAGQAHwBJAGUAOwBdAEoAaQATACUALQBYAG1fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAB4ALgAmAK4AAgAQIgA3TADYANwAKAG8AeQA8qQBwAHEAcgBzAHQAdQB2AHcAeIAOgA+AEIARgBKAE4AUgBWAFqkAegB7AHwAfQB+AH8AgACBAIKAF4AbgByAHoAfgCGAI4AmgCqAJV8QE1hEUE1Db21wb3VuZEluZGV4ZXNfEBBYRF9QU0tfZWxlbWVudElEXxAZWERQTVVuaXF1ZW5lc3NDb25zdHJhaW50c18QGlhEX1BTS192ZXJzaW9uSGFzaE1vZGlmaWV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lFacVqBXnFekV6xXtFe8V8BXxFfIV8xX1FfcV+BX5FfsV/BY7Fj0WPxZBFkMWRBZFFkYWRxZJFksWTBZNFk8WUBZTFpIWlBaWFpgWmhabFpwWnRaeFqAWohajFqQWphanFuYW6BbqFuwW7hbvFvAW8RbyFvQW9hb3FvgW+hb7FzoXPBc+F0AXQhdDF0QXRRdGF0gXShdLF0wXThdPF1gXZhdzF4EXjhehF7gXyhgVGDgYWBh4GHoYfBh+GIAYghiDGIQYhhiHGIkYihiMGI4YjxiQGJIYkxiYGKUYqhisGK4Ysxi1GLcYuRjeGQIZKRlNGU8ZURlTGVUZVxlZGVoZXBlpGXoZfBl+GYAZghmEGYYZiBmKGZsZnRmfGaEZoxmlGacZqRmrGa0Z7BnuGfAZ8hn0GfUZ9hn3GfgZ+hn8Gf0Z/hoAGgEaQBpCGkQaRhpIGkkaShpLGkwaThpQGlEaUhpUGlUalBqWGpgamhqcGp0anhqfGqAaohqkGqUaphqoGqkathq3Grgauhr5Gvsa/Rr/GwEbAhsDGwQbBRsHGwkbChsLGw0bDhtNG08bURtTG1UbVhtXG1gbWRtbG10bXhtfG2EbYhuhG6MbpRunG6kbqhurG6wbrRuvG7EbshuzG7Ubthv1G/cb+Rv7G/0b/hv/HAAcARwDHAUcBhwHHAkcChxJHEscTRxPHFEcUhxTHFQcVRxXHFkcWhxbHF0cXhyDHKcczhzyHPQc9hz4HPoc/Bz+HP8dAR0OHR0dHx0hHSMdJR0nHSkdKx06HTwdPh1AHUIdRB1GHUgdSh2JHYsdjR2PHZEdkh2THZQdlR2XHZkdmh2bHZ0dnh3dHd8d4R3jHeUd5h3nHegd6R3rHe0d7h3vHfEd8h4xHjMeNR43HjkeOh47HjwePR4/HkEeQh5DHkUeRh6FHoceiR6LHo0ejh6PHpAekR6THpUelh6XHpkemh7ZHtse3R7fHuEe4h7jHuQe5R7nHuke6h7rHu0e7h8tHy8fMR8zHzUfNh83HzgfOR87Hz0fPh8/H0EfQh+BH4MfhR+HH4kfih+LH4wfjR+PH5Efkh+TH5Uflh/hIAQgJCBEIEYgSCBKIEwgTiBPIFAgUiBTIFUgViBYIFogWyBcIF4gXyBkIHEgdiB4IHogfyCBIIMghSCqIM4g9SEZIRshHSEfISEhIyElISYhKCE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 + + name + + + \ No newline at end of file diff --git a/DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/.xccurrentversion b/DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..13cfc29 --- /dev/null +++ b/DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + AnotherTestModel 2.xcdatamodel + + diff --git a/DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel 2.xcdatamodel/contents b/DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel 2.xcdatamodel/contents new file mode 100644 index 0000000..0efcd6b --- /dev/null +++ b/DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel 2.xcdatamodel/contents @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel.xcdatamodel/contents b/DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel.xcdatamodel/contents new file mode 100644 index 0000000..3856592 --- /dev/null +++ b/DataKernelTests/Classes/Migration/AnotherTestModel.xcdatamodeld/AnotherTestModel.xcdatamodel/contents @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/DataKernelTests/Classes/Migration/DKMigrationTests.swift b/DataKernelTests/Classes/Migration/DKMigrationTests.swift new file mode 100644 index 0000000..4e3d5c2 --- /dev/null +++ b/DataKernelTests/Classes/Migration/DKMigrationTests.swift @@ -0,0 +1,41 @@ +import Foundation +import XCTest +import CoreData +@testable import DataKernel + +class DKMigrationTests: XCTestCase { + func testMigrationInPlaceWithMappingModel() throws { + let dbUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("db.sqlite") + try DKStoreFile(url: dbUrl).remove() + + let bundle = Bundle(for: type(of: self)) + let models = DKModels(name: "AnotherTestModel", bundle: bundle) + + try TestData().generateAnotherTestModelV1(dbUrl: dbUrl) + + let fromModel = models.model(name: "AnotherTestModel") + let toModel = models.model(name: "AnotherTestModel 2") + let migration = DKMigration( + from: fromModel, + to: toModel, + mapping: NSMappingModel(from: [models.bundle], forSourceModel: fromModel, destinationModel: toModel)) + let storeType = NSSQLiteStoreType + XCTAssertTrue(migration.canPerformLightweightMigration(sourceStoreType: storeType, targetStoreType: storeType)) + try migration.performLightweightMigration(url: dbUrl, storeType: storeType) + + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: try models.currentModel()) + try coordinator.addPersistentStore( + ofType: NSSQLiteStoreType, + configurationName: nil, + at: dbUrl, + options: nil) + let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + context.persistentStoreCoordinator = coordinator + + let request = NSFetchRequest(entityName: "FioEntity") + let fios = try context.fetch(request) + XCTAssertEqual(fios.count, 1) + let fio = fios.first! + XCTAssertEqual(fio.value(forKey: "name") as? String, "Ivanov Ivan") + } +} diff --git a/DataKernelTests/Classes/Migration/DKModelsTests.swift b/DataKernelTests/Classes/Migration/DKModelsTests.swift new file mode 100644 index 0000000..1384cf7 --- /dev/null +++ b/DataKernelTests/Classes/Migration/DKModelsTests.swift @@ -0,0 +1,14 @@ +import Foundation +import XCTest +@testable import DataKernel + +class DKModelsTests: XCTestCase { + func testFiles () { + let bundle = Bundle(for: type(of: self)) + let models = DKModels(name: "Model", bundle: bundle) + let fileNames = models.files.map { + URL(fileURLWithPath: $0).lastPathComponent + }.sorted() + XCTAssertEqual(fileNames, ["Model 2.mom", "Model 3.mom", "Model.mom"]) + } +} diff --git a/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift b/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift new file mode 100644 index 0000000..f9cd1d7 --- /dev/null +++ b/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift @@ -0,0 +1,54 @@ +import Foundation +import XCTest +import CoreData +@testable import DataKernel + +class DKProgressiveStoreLoaderTests: XCTestCase { + func testMigrationFromV1ToV2() throws { + let dbUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("db.sqlite") + try DKStoreFile(url: dbUrl).remove() + + let testData = TestData() + try testData.generateModelV1(dbUrl: dbUrl) + + let bundle = Bundle(for: type(of: self)) + let models = DKModels(name: "Model", bundle: bundle) + + let loader = DKProgressiveStoreLoader(models: models) + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: models.model(name: "Model 2")) + try loader.append(store: dbUrl, ofType: NSSQLiteStoreType, to: coordinator) + let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + context.persistentStoreCoordinator = coordinator + + let request = NSFetchRequest(entityName: "PersonEntity") + let persons = try context.fetch(request) + XCTAssertEqual(persons.count, 1) + let person = persons.first! + XCTAssertEqual(person.value(forKey: "fullName") as? String, "Ivanov Ivan") + } + + func testMigrationFromV1ToV3() throws { + let dbUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("db.sqlite") + if FileManager.default.fileExists(atPath: dbUrl.path, isDirectory: nil) { + try! FileManager.default.removeItem(at: dbUrl) + } + let bundle = Bundle(for: type(of: self)) + let models = DKModels(name: "Model", bundle: bundle) + let testData = TestData() + try testData.generateModelV1(dbUrl: dbUrl) + + let loader = DKProgressiveStoreLoader(models: models) + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: try models.currentModel()) + try loader.append(store: dbUrl, ofType: NSSQLiteStoreType, to: coordinator) + let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + context.persistentStoreCoordinator = coordinator + + XCTAssertTrue(FileManager.default.fileExists(atPath: dbUrl.path)) + let request = NSFetchRequest(entityName: "PersonEntity") + let persons = try context.fetch(request) + XCTAssertEqual(persons.count, 1) + let person = persons.first! + XCTAssertEqual(person.value(forKey: "lastName") as? String, "Ivanov") + XCTAssertEqual(person.value(forKey: "firstName") as? String, "Ivan") + } +} diff --git a/DataKernelTests/Classes/Migration/DKStoreFileTests.swift b/DataKernelTests/Classes/Migration/DKStoreFileTests.swift new file mode 100644 index 0000000..68b54b7 --- /dev/null +++ b/DataKernelTests/Classes/Migration/DKStoreFileTests.swift @@ -0,0 +1,25 @@ +import Foundation +import XCTest +@testable import DataKernel + +class DKStoreFileTests: XCTestCase { + var dirUrl: URL! + + override func setUp() { + dirUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("YxSQLiteFileTests") + try! FileManager.default.removeAll(fromDirectory: dirUrl.path) + } + + func testRemove() throws { + let dbUrl = dirUrl.appendingPathComponent("db.sqlite") + try TestData().generateModelV1(dbUrl: dbUrl) + try DKStoreFile(url: dbUrl).remove() + let actualFileNames = try FileManager.default.contentsOfDirectory(atPath: dirUrl.path) + XCTAssertEqual(actualFileNames, [String]()) + } + + func testRemove_fileIsMissed() throws { + let dbUrl = dirUrl.appendingPathComponent("db.sqlite") + try DKStoreFile(url: dbUrl).remove() + } +} diff --git a/DataKernelTests/Classes/Migration/DKVersionFromFileNamePolicyTests.swift b/DataKernelTests/Classes/Migration/DKVersionFromFileNamePolicyTests.swift new file mode 100644 index 0000000..7be8968 --- /dev/null +++ b/DataKernelTests/Classes/Migration/DKVersionFromFileNamePolicyTests.swift @@ -0,0 +1,31 @@ +import Foundation +import XCTest +@testable import DataKernel + +class DKVersionFromFileNamePolicyTests: XCTestCase { + let versionPolicy = DKVersionFromFileNamePolicy(prefix: "Model") + let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory()) + + func test() { + XCTAssertEqual( + try! versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("Model 31.mom")), + 31) + XCTAssertEqual( + try! versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("Model 2.mom")), + 2) + } + + func test_defaultVersion() { + XCTAssertEqual( + try! versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("Model.mom")), + 1) + } + + func test_wrongPrefixThrowsError() throws { + do { + let _ = try versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("AnotherTestModel 2.mom")) + XCTFail("Must be error") + } catch DKMigrationError.failedToGetVersionFromFilename { + } + } +} diff --git a/DataKernelTests/Classes/Migration/FileManager+DKTests.swift b/DataKernelTests/Classes/Migration/FileManager+DKTests.swift new file mode 100644 index 0000000..bfd701b --- /dev/null +++ b/DataKernelTests/Classes/Migration/FileManager+DKTests.swift @@ -0,0 +1,12 @@ +import Foundation + +extension FileManager { + func removeAll(fromDirectory path: String) throws { + try createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) + let fileNames = try contentsOfDirectory(atPath: path) + for fileName in fileNames { + let fileUrl = URL(fileURLWithPath: path).appendingPathComponent(fileName) + try removeItem(atPath: fileUrl.path) + } + } +} \ No newline at end of file diff --git a/DataKernelTests/Classes/Migration/Model.xcdatamodeld/.xccurrentversion b/DataKernelTests/Classes/Migration/Model.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..419fdc4 --- /dev/null +++ b/DataKernelTests/Classes/Migration/Model.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Model 3.xcdatamodel + + diff --git a/DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 2.xcdatamodel/contents b/DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 2.xcdatamodel/contents new file mode 100644 index 0000000..204b563 --- /dev/null +++ b/DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 2.xcdatamodel/contents @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 3.xcdatamodel/contents b/DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 3.xcdatamodel/contents new file mode 100644 index 0000000..1ee022f --- /dev/null +++ b/DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 3.xcdatamodel/contents @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model.xcdatamodel/contents b/DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model.xcdatamodel/contents new file mode 100644 index 0000000..cd022a4 --- /dev/null +++ b/DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model.xcdatamodel/contents @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/DataKernelTests/Classes/Migration/ModelMapping.xcmappingmodel/xcmapping.xml b/DataKernelTests/Classes/Migration/ModelMapping.xcmappingmodel/xcmapping.xml new file mode 100644 index 0000000..7f7848d --- /dev/null +++ b/DataKernelTests/Classes/Migration/ModelMapping.xcmappingmodel/xcmapping.xml @@ -0,0 +1,77 @@ + + + + + + 134481920 + 4B210C03-0ABD-47BA-8A77-9FD842F56CCC + 105 + + + + NSPersistenceFrameworkVersion + 754 + NSStoreModelVersionHashes + + XDDevAttributeMapping + + 0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc= + + XDDevEntityMapping + + qeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI= + + XDDevMappingModel + + EqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ= + + XDDevPropertyMapping + + XN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA= + + XDDevRelationshipMapping + + akYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs= + + + NSStoreModelVersionHashesVersion + 3 + NSStoreModelVersionIdentifiers + + + + + + + + + DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 2.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGAugC6VgkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRv +cBIAAYagrxBnAAcACAAXADMANAA1AD0APgBZAFoAWwBhAGIAbgCEAIUAhgCHAIgAiQCKAIsAjACNAKYAqQCwALYAxQDGANUA2ADnAPYA+QBYAQkBGAEcASABLwE1ATYBPgFNAU4BVwFdAV4BcwF0AXwBfQF+AYoBngGfAaABoQGiAaMBpAGlAaYBtQHEAdMB1wHmAfUB9gIFAhQCFQIkAjACQgJDAkQCRQJGAkcCSAJJAlgCZwJ2AoUChgKVAqQCswK7ArwCvwLIAswC0ALUAtwC3wLjAuRVJG51bGzXAAkACgALAAwADQAOAA8AEAARABIAEwAUABMAFl8QD194ZF9yb290UGFja2FnZVYkY2xhc3NcX3hkX2NvbW1lbnRzXxAQX3hkX21vZGVsTWFuYWdlcl8QFV9jb25maWd1cmF0aW9uc0J5TmFtZV1feGRfbW9kZWxOYW1lXxAXX21vZGVsVmVyc2lvbklkZW50aWZpZXKAAoBmgGOAAIBkgACAZd4AGAAZABoAGwAcAB0AHgAKAB8AIAAhACIAIwAkACUAJgAnACgAJQATACsALAAtAC4ALwAlACUAE18QHFhEQnVja2V0Rm9yQ2xhc3Nlc3dhc0VuY29kZWRfEBpYREJ1Y2tldEZvclBhY2thZ2Vzc3RvcmFnZV8QHFhEQnVja2V0Rm9ySW50ZXJmYWNlc3N0b3JhZ2VfEA9feGRfb3duaW5nTW9kZWxfEB1YREJ1Y2tldEZvclBhY2thZ2Vzd2FzRW5jb2RlZFZfb3duZXJfEBtYREJ1Y2tldEZvckRhdGFUeXBlc3N0b3JhZ2VbX3Zpc2liaWxpdHlfEBlYREJ1Y2tldEZvckNsYXNzZXNzdG9yYWdlVV9uYW1lXxAfWERCdWNrZXRGb3JJbnRlcmZhY2Vzd2FzRW5jb2RlZF8QHlhEQnVja2V0Rm9yRGF0YVR5cGVzd2FzRW5jb2RlZF8QEF91bmlxdWVFbGVtZW50SUSABIBhgF+AAYAEgACAYIBiEACABYADgASABIAAUFNZRVPTADYANwAKADgAOgA8V05TLmtleXNaTlMub2JqZWN0c6EAOYAGoQA7gAeAJlxQZXJzb25FbnRpdHnfEBAAPwBAAEEAQgAdAEMARAAfAEUARgAKACEARwBIACQASQBKAEsAJQAlABAATwBQAC0AJQBKAFMAOQBKAFYAVwBYXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zZHVwbGljYXRlc18QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNzdG9yYWdlW19pc0Fic3RyYWN0gAmALoAEgASAAoAKgFyABIAJgF6ABoAJgF2ACAgSsAOmlldvcmRlcmVk0wA2ADcACgBcAF4APKEAXYALoQBfgAyAJl5YRF9QU3RlcmVvdHlwZdkAHQAhAGMACgAkAGQAHwBJAGUAOwBdAEoAaQATACUALQBYAG1fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAB4ALgAmALYAAgAQIgA3TADYANwAKAG8AeQA8qQBwAHEAcgBzAHQAdQB2AHcAeIAOgA+AEIARgBKAE4AUgBWAFqkAegB7AHwAfQB+AH8AgACBAIKAF4AbgB2AH4AggCKAJIAngCuAJl8QE1hEUE1Db21wb3VuZEluZGV4ZXNfEBBYRF9QU0tfZWxlbWVudElEXxAZWERQTVVuaXF1ZW5lc3NDb25zdHJhaW50c18QGlhEX1BTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAZWERfUFNLX2ZldGNoUmVxdWVzdHNBcnJheV8QEVhEX1BTS19pc0Fic3RyYWN0XxAPWERfUFNLX3VzZXJJbmZvXxATWERfUFNLX2NsYXNzTWFwcGl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ooIAZ0wA2ADcACgLgAuEAPKCggCZQ0gCqAKsC5QLmWVhEUE1Nb2RlbKMC5QLnAK9XWERNb2RlbF8QD05TS2V5ZWRBcmNoaXZlctEC6gAoVHJvb3SAAQAIABkAIgArADUAOgA/ARABFgEzAUUBTAFZAWwBhAGSAawBrgGwAbIBtAG2AbgBugHzAhICLwJOAmACgAKHAqUCsQLNAtMC9QMWAykDKwMtAy8DMQMzAzUDNwM5AzsDPQM/A0EDQwNFA0YDSgNXA18DagNtA28DcgN0A3YDgwPGA+oEDgQxBFgEeASfBMYE5gUKBS4FOgU8BT4FQAVCBUQFRgVIBUoFTAVOBVAFUgVUBVYFVwVcBWQFcQV0BXYFeQV7BX0FjAWxBdUF/AYgBiIGJAYmBigGKgYsBi0GLwY8Bk8GUQZTBlUGVwZZBlsGXQZfBmEGdAZ2BngGegZ8Bn4GgAaCBoQGhgaIBp4GsQbNBuoHBgcaBywHQgdbB5oHoAepB7YHwgfMB9YH4QfsB/kIAQgDCAUIBwgJCAoICwgMCA0IDwgRCBIIEwgVCBYIHwggCCIIKwg2CD8ITghVCF0IZghvCIIIiwieCLUIxwkGCQgJCgkMCQ4JDwkQCREJEgkUCRYJFwkYCRoJGwkmCWUJZwlpCWsJbQluCW8JcAlxCXMJdQl2CXcJeQl6CYMJhAmGCcUJxwnJCcsJzQnOCc8J0AnRCdMJ1QnWCdcJ2QnaChkKGwodCh8KIQoiCiMKJAolCicKKQoqCisKLQouCjcKOAo6CnkKewp9Cn8KgQqCCoMKhAqFCocKiQqKCosKjQqOCo8KzgrQCtIK1ArWCtcK2ArZCtoK3AreCt8K4AriCuMK8ArxCvIK9Ar9CxMLGgsnC2YLaAtqC2wLbgtvC3ALcQtyC3QLdgt3C3gLegt7C5QLlguYC5oLmwudC7QLvQvLC9gL5gv7DA8MJgw4DHcMeQx7DH0MfwyADIEMggyDDIUMhwyIDIkMiwyMDJkMogy3DMYM2wzpDP4NEg0pDTsNSA1LDU0NUA1SDVQNXQ2oDcsN6w4LDg0ODw4RDhMOFQ4WDhcOGQ4aDhwOHQ4fDiEOIg4jDiUOJg4rDjgOPQ4/DkEORg5IDkoOTA5hDnYOmw6/DuYPCg8MDw4PEA8SDxQPFg8XDxkPJg83DzkPOw89Dz8PQQ9DD0UPRw9YD1oPXA9eD2APYg9kD2YPaA9qD4gPpg+5D80P4g//EBMQKRBoEGoQbBBuEHAQcRByEHMQdBB2EHgQeRB6EHwQfRC8EL4QwBDCEMQQxRDGEMcQyBDKEMwQzRDOENAQ0REQERIRFBEWERgRGREaERsRHBEeESARIREiESQRJREyETMRNBE2EXURdxF5EXsRfRF+EX8RgBGBEYMRhRGGEYcRiRGKEckRyxHNEc8R0RHSEdMR1BHVEdcR2RHaEdsR3RHeEd8SHhIgEiISJBImEicSKBIpEioSLBIuEi8SMBIyEjMSchJ0EnYSeBJ6EnsSfBJ9En4SgBKCEoMShBKGEocSjBLLEs0SzxLREtMS1BLVEtYS1xLZEtsS3BLdEt8S4BMFEykTUBN0E3YTeBN6E3wTfhOAE4ETgxOQE58ToROjE6UTpxOpE6sTrRO8E74TwBPCE8QTxhPIE8oTzBPsFBcUMRRKFGQUhBSnFOYU6BTqFOwU7hTvFPAU8RTyFPQU9hT3FPgU+hT7FToVPBU+FUAVQhVDFUQVRRVGFUgVShVLFUwVThVPFY4VkBWSFZQVlhWXFZgVmRWaFZwVnhWfFaAVohWjFeIV5BXmFegV6hXrFewV7RXuFfAV8hXzFfQV9hX3FfoWORY7Fj0WPxZBFkIWQxZEFkUWRxZJFkoWSxZNFk4WjRaPFpEWkxaVFpYWlxaYFpkWmxadFp4WnxahFqIW4RbjFuUW5xbpFuoW6xbsFu0W7xbxFvIW8xb1FvYW/xcNFxoXKBc1F0gXXxdxF3wXhReGF4gXkRecF6sXthfEF9kX7RgEGBYYIxgkGCUYJxg0GDUYNhg4GEUYRhhHGEkYUhhhGG4YfRiPGKMYuhjMGNUY1hjYGOUY5hjnGOkY6hjzGP0ZBBkMGR4ZIxkoAAAAAAAAAgIAAAAAAAAC7AAAAAAAAAAAAAAAAAAAGSo= + + DataKernelTests/Classes/Migration/Model.xcdatamodeld/Model 3.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGBCoEK1gkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRv +cBIAAYagrxB9AAcACAAXADMANAA1AD0APgBZAFoAWwBhAGIAbgCEAIUAhgCHAIgAiQCKAIsAjACNAKYAqQCwALYAxQDGANUA2ADnAPYA+QBYAQkBGAEcASABLwE1ATYBPgFNAU4BVwFfAWABYQF2AXcBfwGAAYEBjQGhAaIBowGkAaUBpgGnAagBqQG4AccB1gHaAekB+AIHAhYCJQIxAkMCRAJFAkYCRwJIAkkCSgJZAmgCdwKGAocClgKlArQCvALRAtIC2gLmAvoDCQMYAycDKwM6A0kDWANnA3YDggOUA6MDsgPBA9AD3wPuA/0D/gQBBAoEDgQSBBYEHgQhBCUEJlUkbnVsbNcACQAKAAsADAANAA4ADwAQABEAEgATABQAEwAWXxAPX3hkX3Jvb3RQYWNrYWdlViRjbGFzc1xfeGRfY29tbWVudHNfEBBfeGRfbW9kZWxNYW5hZ2VyXxAVX2NvbmZpZ3VyYXRpb25zQnlOYW1lXV94ZF9tb2RlbE5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgHyAeYAAgHqAAIB73gAYABkAGgAbABwAHQAeAAoAHwAgACEAIgAjACQAJQAmACcAKAAlABMAKwAsAC0ALgAvACUAJQATXxAcWERCdWNrZXRGb3JDbGFzc2Vzd2FzRW5jb2RlZF8QGlhEQnVja2V0Rm9yUGFja2FnZXNzdG9yYWdlXxAcWERCdWNrZXRGb3JJbnRlcmZhY2Vzc3RvcmFnZV8QD194ZF9vd25pbmdNb2RlbF8QHVhEQnVja2V0Rm9yUGFja2FnZXN3YXNFbmNvZGVkVl9vd25lcl8QG1hEQnVja2V0Rm9yRGF0YVR5cGVzc3RvcmFnZVtfdmlzaWJpbGl0eV8QGVhEQnVja2V0Rm9yQ2xhc3Nlc3N0b3JhZ2VVX25hbWVfEB9YREJ1Y2tldEZvckludGVyZmFjZXN3YXNFbmNvZGVkXxAeWERCdWNrZXRGb3JEYXRhVHlwZXN3YXNFbmNvZGVkXxAQX3VuaXF1ZUVsZW1lbnRJRIAEgHeAdYABgASAAIB2gHgQAIAFgAOABIAEgABQU1lFU9MANgA3AAoAOAA6ADxXTlMua2V5c1pOUy5vYmplY3RzoQA5gAahADuAB4AmXFBlcnNvbkVudGl0ed8QEAA/AEAAQQBCAB0AQwBEAB8ARQBGAAoAIQBHAEgAJABJAEoASwAlACUAEABPAFAALQAlAEoAUwA5AEoAVgBXAFhfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACYAugASABIACgAqAcoAEgAmAdIAGgAmAc4AICBJqduUkV29yZGVyZWTTADYANwAKAFwAXgA8oQBdgAuhAF+ADIAmXlhEX1BTdGVyZW90eXBl2QAdACEAYwAKACQAZAAfAEkAZQA7AF0ASgBpABMAJQAtAFgAbV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYAtgACABAiADdMANgA3AAoAbwB5ADypAHAAcQByAHMAdAB1AHYAdwB4gA6AD4AQgBGAEoATgBSAFYAWqQB6AHsAfAB9AH4AfwCAAIEAgoAXgBuAHYAfgCCAIoAkgCeAK4AmXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJ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YmhicGMEY5RkMGTAZMhk0GTYZOBk6GTwZPRk/GUwZXRlfGWEZYxllGWcZaRlrGW0ZfhmAGYIZhBmGGYgZihmMGY4ZkBnPGdEZ0xnVGdcZ2BnZGdoZ2xndGd8Z4BnhGeMZ5BojGiUaJxopGisaLBotGi4aLxoxGjMaNBo1GjcaOBp3Gnkaexp9Gn8agBqBGoIagxqFGocaiBqJGosajBqZGpoamxqdGtwa3hrgGuIa5BrlGuYa5xroGuoa7BrtGu4a8BrxGzAbMhs0GzYbOBs5GzobOxs8Gz4bQBtBG0IbRBtFG4QbhhuIG4objBuNG44bjxuQG5IblBuVG5YbmBuZG9gb2hvcG94b4BvhG+Ib4xvkG+Yb6BvpG+ob7BvtHCwcLhwwHDIcNBw1HDYcNxw4HDocPBw9HD4cQBxBHGYcihyxHNUc1xzZHNsc3RzfHOEc4hzkHPEdAB0CHQQdBh0IHQodDB0OHR0dHx0hHSMdJR0nHSkdKx0tHWwdbh1wHXIddB11HXYddx14HXodfB19HX4dgB2BHcAdwh3EHcYdyB3JHcodyx3MHc4d0B3RHdId1B3VHhQeFh4YHhoeHB4dHh4eHx4gHiIeJB4lHiYeKB4pHmgeah5sHm4ecB5xHnIecx50HnYeeB55HnoefB59Hrwevh7AHsIexB7FHsYexx7IHsoezB7NHs4e0B7RHxAfEh8UHxYfGB8ZHxofGx8cHx4fIB8hHyIfJB8lH2QfZh9oH2ofbB9tH24fbx9wH3IfdB91H3YfeB95H4QfjR+OH5AfmR+kH7Mfvh/MH+Ef9SAMIB4gKyAsIC0gLyA8ID0gPiBAIE0gTiBPIFEgWiBpIHYghSCXIKsgwiDUIN0g3iDgIO0g7iDvIPEg8iD7IQUhDCEUISYhKyEwAAAAAAAAAgIAAAAAAAAELgAAAAAAAAAAAAAAAAAAITI= + + + + + TestModelV2ToV3Mapping + PersonEntity + Undefined + 1 + PersonEntity + 1 + + + + + + firstName + + + + lastName + + + \ No newline at end of file diff --git a/DataKernelTests/Classes/Migration/TestData.swift b/DataKernelTests/Classes/Migration/TestData.swift new file mode 100644 index 0000000..5eb3d79 --- /dev/null +++ b/DataKernelTests/Classes/Migration/TestData.swift @@ -0,0 +1,37 @@ +import Foundation +import XCTest +import CoreData +@testable import DataKernel + +class TestData { + func generateModelV1(dbUrl: URL) throws { + let bundle = Bundle(for: type(of: self)) + let models = DKModels(name: "Model", bundle: bundle) + let context: NSManagedObjectContext? = try managedContext(url: dbUrl, model: models.model(name: "Model")) + let testEntity = NSEntityDescription.insertNewObject(forEntityName: "TestEntity", into: context!) + testEntity.setValue("Ivanov Ivan", forKey: "name") + try context!.save() + } + + func generateAnotherTestModelV1(dbUrl: URL) throws { + let bundle = Bundle(for: type(of: self)) + let models = DKModels(name: "AnotherTestModel", bundle: bundle) + let context: NSManagedObjectContext? = try managedContext(url: dbUrl, model: models.model(name: "AnotherTestModel")) + let testEntity = NSEntityDescription.insertNewObject(forEntityName: "FioEntity", into: context!) + testEntity.setValue("Ivanov", forKey: "lastName") + testEntity.setValue("Ivan", forKey: "firstName") + try context!.save() + } + + func managedContext(url: URL, model: NSManagedObjectModel) throws -> NSManagedObjectContext { + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) + try coordinator.addPersistentStore( + ofType: NSSQLiteStoreType, + configurationName: nil, + at: url, + options: nil) + let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + context.persistentStoreCoordinator = coordinator + return context + } +} diff --git a/DataKernelTests/Classes/Migration/TestModelV2ToV3Mapping.swift b/DataKernelTests/Classes/Migration/TestModelV2ToV3Mapping.swift new file mode 100644 index 0000000..0353c9c --- /dev/null +++ b/DataKernelTests/Classes/Migration/TestModelV2ToV3Mapping.swift @@ -0,0 +1,19 @@ +import Foundation +import CoreData + +@objc(TestModelV2ToV3Mapping) +class TestModelV2ToV3Mapping: NSEntityMigrationPolicy { + override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws { + guard let name = sInstance.value(forKey: "fullName") as? String else { + fatalError("Property 'fullName' not found") + } + let nameParts = name.components(separatedBy: " ") + + let dInstance = NSEntityDescription.insertNewObject( + forEntityName: "PersonEntity", + into: manager.destinationContext) + dInstance.setValue(nameParts[0], forKey: "lastName") + dInstance.setValue(nameParts[1], forKey: "firstName") + manager.associate(sourceInstance: sInstance, withDestinationInstance: dInstance, for: mapping) + } +} From 943841ed2c010163bd0248bad4dce7eaf8e0237b Mon Sep 17 00:00:00 2001 From: Pavel Koltsov Date: Tue, 14 May 2019 11:25:57 +0500 Subject: [PATCH 2/3] Swift 5 migration --- DataKernel.xcodeproj/project.pbxproj | 45 ++++++++++++++----- .../xcschemes/DataKernel-iOS.xcscheme | 2 +- .../xcschemes/DataKernel.xcscheme | 2 +- DataKernel/AppDelegate.swift | 2 +- .../Migration/DKMigrationFactory.swift | 2 +- .../Classes/Migration/DKVersionPolicy.swift | 2 +- .../Classes/Helpers/RequestTests.swift | 4 +- .../DKProgressiveStoreLoaderTests.swift | 4 +- 8 files changed, 44 insertions(+), 19 deletions(-) diff --git a/DataKernel.xcodeproj/project.pbxproj b/DataKernel.xcodeproj/project.pbxproj index 63c10fe..7202d24 100644 --- a/DataKernel.xcodeproj/project.pbxproj +++ b/DataKernel.xcodeproj/project.pbxproj @@ -462,7 +462,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = mrdekk; TargetAttributes = { 94BB39051CD9B8AA001A2DBE = { @@ -482,7 +482,7 @@ }; buildConfigurationList = 94F74A671CD51764000F1F4D /* Build configuration list for PBXProject "DataKernel" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -668,13 +668,14 @@ 94BB390B1CD9B8AA001A2DBE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - EMBEDDED_CONTENT_CONTAINS_SWIFT = NO; INFOPLIST_FILE = "DataKernel-iOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -682,7 +683,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "ru.mrdekk.DataKernel-iOS"; PRODUCT_NAME = DataKernel; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -691,13 +692,14 @@ 94BB390C1CD9B8AA001A2DBE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - EMBEDDED_CONTENT_CONTAINS_SWIFT = NO; INFOPLIST_FILE = "DataKernel-iOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -705,7 +707,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "ru.mrdekk.DataKernel-iOS"; PRODUCT_NAME = DataKernel; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -715,17 +717,28 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -760,17 +773,28 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -789,6 +813,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -802,7 +827,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.mrdekk.DataKernel; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -814,7 +839,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.mrdekk.DataKernel; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -826,7 +851,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.mrdekk.DataKernelTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DataKernel.app/DataKernel"; }; name = Debug; @@ -839,7 +864,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ru.mrdekk.DataKernelTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DataKernel.app/DataKernel"; }; name = Release; diff --git a/DataKernel.xcodeproj/xcshareddata/xcschemes/DataKernel-iOS.xcscheme b/DataKernel.xcodeproj/xcshareddata/xcschemes/DataKernel-iOS.xcscheme index 3caf4ae..60b1e7c 100644 --- a/DataKernel.xcodeproj/xcshareddata/xcschemes/DataKernel-iOS.xcscheme +++ b/DataKernel.xcodeproj/xcshareddata/xcschemes/DataKernel-iOS.xcscheme @@ -1,6 +1,6 @@ Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } diff --git a/DataKernel/Classes/Migration/DKMigrationFactory.swift b/DataKernel/Classes/Migration/DKMigrationFactory.swift index aac675f..62d6edf 100644 --- a/DataKernel/Classes/Migration/DKMigrationFactory.swift +++ b/DataKernel/Classes/Migration/DKMigrationFactory.swift @@ -31,7 +31,7 @@ public struct DKMigrationFactory { } private func sortedModelFilesByVersionDesc() throws -> [String] { - let fileAndVersion = try models.files.flatMap { (file: String) -> (file: String, version: Int)? in + let fileAndVersion = try models.files.compactMap { (file: String) -> (file: String, version: Int)? in let modelFile = URL(fileURLWithPath: file) let version = try self.versionPolicy.version(modelUrl: modelFile) return (file: file, version: version) diff --git a/DataKernel/Classes/Migration/DKVersionPolicy.swift b/DataKernel/Classes/Migration/DKVersionPolicy.swift index 38c1a16..df402e1 100644 --- a/DataKernel/Classes/Migration/DKVersionPolicy.swift +++ b/DataKernel/Classes/Migration/DKVersionPolicy.swift @@ -9,7 +9,7 @@ public struct DKVersionFromFileNamePolicy: DKVersionPolicy { public func version(modelUrl: URL) throws -> Int { let filename = modelUrl.deletingPathExtension().lastPathComponent if filename.hasPrefix(prefix) { - let suffix = filename.substring(from: filename.index(filename.startIndex, offsetBy: prefix.characters.count)) + let suffix = filename.substring(from: filename.index(filename.startIndex, offsetBy: prefix.count)) return Int(suffix.trimmingCharacters(in: CharacterSet.whitespaces)) ?? 1 } throw DKMigrationError.failedToGetVersionFromFilename(filename: filename) diff --git a/DataKernelTests/Classes/Helpers/RequestTests.swift b/DataKernelTests/Classes/Helpers/RequestTests.swift index e11b76f..0e597ae 100644 --- a/DataKernelTests/Classes/Helpers/RequestTests.swift +++ b/DataKernelTests/Classes/Helpers/RequestTests.swift @@ -41,8 +41,8 @@ class RequestTests: XCTestCase { } func testRequestSortWithKeyAndAscendingAndComparator() { - let descriptor = NSSortDescriptor(key: "model", ascending: true, comparator: { _ in return ComparisonResult.orderedSame }) - let request: Request = Request().sort("model", ascending: true, comparator: { _ in return ComparisonResult.orderedSame }) + let descriptor = NSSortDescriptor(key: "model", ascending: true, comparator: { _, _ in return ComparisonResult.orderedSame }) + let request: Request = Request().sort("model", ascending: true, comparator: { _, _ in return ComparisonResult.orderedSame }) XCTAssertEqual(descriptor.key, request.sort?.key) XCTAssertEqual(descriptor.ascending, request.sort?.ascending) diff --git a/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift b/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift index f9cd1d7..1536401 100644 --- a/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift +++ b/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift @@ -16,7 +16,7 @@ class DKProgressiveStoreLoaderTests: XCTestCase { let loader = DKProgressiveStoreLoader(models: models) let coordinator = NSPersistentStoreCoordinator(managedObjectModel: models.model(name: "Model 2")) - try loader.append(store: dbUrl, ofType: NSSQLiteStoreType, to: coordinator) + _ = try loader.append(store: dbUrl, ofType: NSSQLiteStoreType, to: coordinator) let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) context.persistentStoreCoordinator = coordinator @@ -39,7 +39,7 @@ class DKProgressiveStoreLoaderTests: XCTestCase { let loader = DKProgressiveStoreLoader(models: models) let coordinator = NSPersistentStoreCoordinator(managedObjectModel: try models.currentModel()) - try loader.append(store: dbUrl, ofType: NSSQLiteStoreType, to: coordinator) + _ = try loader.append(store: dbUrl, ofType: NSSQLiteStoreType, to: coordinator) let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) context.persistentStoreCoordinator = coordinator From 924e96ca18e57330d87a0b289632265d9b9457ae Mon Sep 17 00:00:00 2001 From: Pavel Koltsov Date: Tue, 14 May 2019 11:38:48 +0500 Subject: [PATCH 3/3] migration refactored --- .../Classes/Migration/DKMigration.swift | 2 +- .../Classes/Migration/DKMigrationError.swift | 6 +++--- .../Migration/DKMigrationFactory.swift | 4 ++-- DataKernel/Classes/Migration/DKModels.swift | 19 ++++++++++--------- .../Classes/Migration/DKVersionPolicy.swift | 2 +- .../Classes/Migration/DKMigrationTests.swift | 4 ++-- .../DKProgressiveStoreLoaderTests.swift | 4 ++-- .../DKVersionFromFileNamePolicyTests.swift | 8 ++++---- 8 files changed, 25 insertions(+), 24 deletions(-) diff --git a/DataKernel/Classes/Migration/DKMigration.swift b/DataKernel/Classes/Migration/DKMigration.swift index 7f2c090..185cd27 100644 --- a/DataKernel/Classes/Migration/DKMigration.swift +++ b/DataKernel/Classes/Migration/DKMigration.swift @@ -48,7 +48,7 @@ public struct DKMigration { let migrationManager = NSMigrationManager( sourceModel: from, destinationModel: to) - try! migrationManager.migrateStore( + try migrationManager.migrateStore( from: url, sourceType: sourceStoreType, options: nil, diff --git a/DataKernel/Classes/Migration/DKMigrationError.swift b/DataKernel/Classes/Migration/DKMigrationError.swift index bc938f4..8e26dcc 100644 --- a/DataKernel/Classes/Migration/DKMigrationError.swift +++ b/DataKernel/Classes/Migration/DKMigrationError.swift @@ -1,7 +1,7 @@ import Foundation public enum DKMigrationError: Error { - case failedToGetVersionFromFilename(filename: String) - case failedToCreateModel(file: String?) - case failedToBuildMigrationPath + case versionNotFound(filename: String) + case migrationPathNotFound + case modelNotFound(name: String?, file: String?) } diff --git a/DataKernel/Classes/Migration/DKMigrationFactory.swift b/DataKernel/Classes/Migration/DKMigrationFactory.swift index 62d6edf..e2eb362 100644 --- a/DataKernel/Classes/Migration/DKMigrationFactory.swift +++ b/DataKernel/Classes/Migration/DKMigrationFactory.swift @@ -11,7 +11,7 @@ public struct DKMigrationFactory { var targetModel: NSManagedObjectModel? = nil for modelFile in modelFiles { guard let sourceModel = NSManagedObjectModel(contentsOf: URL(fileURLWithPath: modelFile)) else { - throw DKMigrationError.failedToCreateModel(file: modelFile) + throw DKMigrationError.modelNotFound(name: nil, file: modelFile) } if let target = targetModel { let mapping = NSMappingModel(from: [models.bundle], forSourceModel: sourceModel, destinationModel: target) @@ -27,7 +27,7 @@ public struct DKMigrationFactory { } } } - throw DKMigrationError.failedToBuildMigrationPath + throw DKMigrationError.migrationPathNotFound } private func sortedModelFilesByVersionDesc() throws -> [String] { diff --git a/DataKernel/Classes/Migration/DKModels.swift b/DataKernel/Classes/Migration/DKModels.swift index 0cb263e..5f32980 100644 --- a/DataKernel/Classes/Migration/DKModels.swift +++ b/DataKernel/Classes/Migration/DKModels.swift @@ -18,31 +18,32 @@ public struct DKModels { return [] } - public func modelFile(name: String) -> String { + public func modelFile(name: String) throws -> String { guard let file = files.first(where: { name == NSURL(fileURLWithPath: $0, isDirectory: false).deletingPathExtension?.lastPathComponent }) else { - fatalError("Managed Model not found for version \(name)") + throw DKMigrationError.modelNotFound(name: name, file: nil) } return file } - public func model(name: String) -> NSManagedObjectModel { - let file = modelFile(name: name) - guard let managedModel = NSManagedObjectModel(contentsOf: URL(fileURLWithPath: file)) else { - fatalError("Unable to create NSManagedObjectModel for \(name), file = \(file)") - } + public func model(name: String) throws -> NSManagedObjectModel { + let file = try modelFile(name: name) + guard let managedModel = NSManagedObjectModel(contentsOf: URL(fileURLWithPath: file)) + else { + throw DKMigrationError.modelNotFound(name: name, file: file) + } return managedModel } public func currentModel() throws -> NSManagedObjectModel { guard let modelPath = self.bundle.path(forResource: self.name, ofType: "momd") else { - throw DKMigrationError.failedToCreateModel(file: nil) + throw DKMigrationError.modelNotFound(name: self.name, file: nil) } if let model = NSManagedObjectModel(contentsOf: URL(fileURLWithPath: modelPath)) { return model } else { - throw DKMigrationError.failedToCreateModel(file: modelPath) + throw DKMigrationError.modelNotFound(name: self.name, file: modelPath) } } } diff --git a/DataKernel/Classes/Migration/DKVersionPolicy.swift b/DataKernel/Classes/Migration/DKVersionPolicy.swift index df402e1..cc609db 100644 --- a/DataKernel/Classes/Migration/DKVersionPolicy.swift +++ b/DataKernel/Classes/Migration/DKVersionPolicy.swift @@ -12,6 +12,6 @@ public struct DKVersionFromFileNamePolicy: DKVersionPolicy { let suffix = filename.substring(from: filename.index(filename.startIndex, offsetBy: prefix.count)) return Int(suffix.trimmingCharacters(in: CharacterSet.whitespaces)) ?? 1 } - throw DKMigrationError.failedToGetVersionFromFilename(filename: filename) + throw DKMigrationError.versionNotFound(filename: filename) } } diff --git a/DataKernelTests/Classes/Migration/DKMigrationTests.swift b/DataKernelTests/Classes/Migration/DKMigrationTests.swift index 4e3d5c2..7d0500c 100644 --- a/DataKernelTests/Classes/Migration/DKMigrationTests.swift +++ b/DataKernelTests/Classes/Migration/DKMigrationTests.swift @@ -13,8 +13,8 @@ class DKMigrationTests: XCTestCase { try TestData().generateAnotherTestModelV1(dbUrl: dbUrl) - let fromModel = models.model(name: "AnotherTestModel") - let toModel = models.model(name: "AnotherTestModel 2") + let fromModel = try models.model(name: "AnotherTestModel") + let toModel = try models.model(name: "AnotherTestModel 2") let migration = DKMigration( from: fromModel, to: toModel, diff --git a/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift b/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift index 1536401..406d971 100644 --- a/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift +++ b/DataKernelTests/Classes/Migration/DKProgressiveStoreLoaderTests.swift @@ -15,7 +15,7 @@ class DKProgressiveStoreLoaderTests: XCTestCase { let models = DKModels(name: "Model", bundle: bundle) let loader = DKProgressiveStoreLoader(models: models) - let coordinator = NSPersistentStoreCoordinator(managedObjectModel: models.model(name: "Model 2")) + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: try models.model(name: "Model 2")) _ = try loader.append(store: dbUrl, ofType: NSSQLiteStoreType, to: coordinator) let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) context.persistentStoreCoordinator = coordinator @@ -30,7 +30,7 @@ class DKProgressiveStoreLoaderTests: XCTestCase { func testMigrationFromV1ToV3() throws { let dbUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("db.sqlite") if FileManager.default.fileExists(atPath: dbUrl.path, isDirectory: nil) { - try! FileManager.default.removeItem(at: dbUrl) + try FileManager.default.removeItem(at: dbUrl) } let bundle = Bundle(for: type(of: self)) let models = DKModels(name: "Model", bundle: bundle) diff --git a/DataKernelTests/Classes/Migration/DKVersionFromFileNamePolicyTests.swift b/DataKernelTests/Classes/Migration/DKVersionFromFileNamePolicyTests.swift index 7be8968..b288390 100644 --- a/DataKernelTests/Classes/Migration/DKVersionFromFileNamePolicyTests.swift +++ b/DataKernelTests/Classes/Migration/DKVersionFromFileNamePolicyTests.swift @@ -8,16 +8,16 @@ class DKVersionFromFileNamePolicyTests: XCTestCase { func test() { XCTAssertEqual( - try! versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("Model 31.mom")), + try versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("Model 31.mom")), 31) XCTAssertEqual( - try! versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("Model 2.mom")), + try versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("Model 2.mom")), 2) } func test_defaultVersion() { XCTAssertEqual( - try! versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("Model.mom")), + try versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("Model.mom")), 1) } @@ -25,7 +25,7 @@ class DKVersionFromFileNamePolicyTests: XCTestCase { do { let _ = try versionPolicy.version(modelUrl: tmpUrl.appendingPathComponent("AnotherTestModel 2.mom")) XCTFail("Must be error") - } catch DKMigrationError.failedToGetVersionFromFilename { + } catch DKMigrationError.versionNotFound { } } }