From 97d7a276fe73b82d1d59316f45b73b35d8a44c77 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 31 Mar 2017 19:44:18 +0900 Subject: [PATCH] new auto-commit transaction methods --- CoreStore.xcodeproj/project.pbxproj | 10 - .../BaseTests/BaseTestDataTestCase.swift | 69 +- CoreStoreTests/BridgingTests.m | 16 +- CoreStoreTests/FetchTests.swift | 2948 +++++++++-------- CoreStoreTests/ImportTests.swift | 1601 ++++----- CoreStoreTests/ListObserverTests.swift | 192 +- CoreStoreTests/ObjectObserverTests.swift | 80 +- CoreStoreTests/TransactionTests.swift | 865 ++--- .../NSManagedObject+Convenience.swift | 4 +- Sources/CoreStoreError.swift | 19 +- .../BaseDataTransaction+Querying.swift | 11 +- .../DataStack+Querying.swift | 11 +- .../FetchableSource.swift | 6 + .../QueryableSource.swift | 6 + Sources/Importing/ImportableObject.swift | 6 +- .../Importing/ImportableUniqueObject.swift | 9 +- .../Internal/DispatchQueue+CoreStore.swift | 12 +- .../NSManagedObjectContext+Querying.swift | 11 +- .../NSManagedObjectContext+Setup.swift | 2 +- .../NSManagedObjectContext+Transaction.swift | 41 +- ...reStore+CustomDebugStringConvertible.swift | 7 +- Sources/Logging/CoreStore+Logging.swift | 4 + .../CSAsynchronousDataTransaction.swift | 79 +- .../ObjectiveC/CSCoreStore+Transaction.swift | 33 +- .../ObjectiveC/CSDataStack+Transaction.swift | 73 +- Sources/ObjectiveC/CSSaveResult.swift | 2 + .../CSSynchronousDataTransaction.swift | 67 +- .../ObjectiveC/CSUnsafeDataTransaction.swift | 83 +- Sources/Observing/ListMonitor.swift | 8 +- Sources/Setup/CoreStore+Setup.swift | 7 +- Sources/Setup/DataStack.swift | 6 +- .../AsynchronousDataTransaction.swift | 182 +- .../Transactions/BaseDataTransaction.swift | 2 +- .../Transactions/CoreStore+Transaction.swift | 61 +- .../Transactions/DataStack+Transaction.swift | 153 +- Sources/Transactions/SaveResult.swift | 1 + .../SynchronousDataTransaction.swift | 194 +- Sources/Transactions/TransactionResult.swift | 58 - .../Transactions/UnsafeDataTransaction.swift | 32 +- 39 files changed, 3739 insertions(+), 3232 deletions(-) delete mode 100644 Sources/Transactions/TransactionResult.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 7987477..3d8e387 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -398,10 +398,6 @@ B59FA0B11CCBACA7007C9BCA /* ICloudStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59FA0AD1CCBAC95007C9BCA /* ICloudStore.swift */; }; B59FA0B21CCBACA8007C9BCA /* ICloudStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59FA0AD1CCBAC95007C9BCA /* ICloudStore.swift */; }; B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A261201B64BFDB006EB6D3 /* MigrationType.swift */; }; - B5A27F861E857C5300203C3E /* TransactionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A27F851E857C5300203C3E /* TransactionResult.swift */; }; - B5A27F871E857C5300203C3E /* TransactionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A27F851E857C5300203C3E /* TransactionResult.swift */; }; - B5A27F881E857C5300203C3E /* TransactionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A27F851E857C5300203C3E /* TransactionResult.swift */; }; - B5A27F891E857C5300203C3E /* TransactionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A27F851E857C5300203C3E /* TransactionResult.swift */; }; B5A5F2661CAEC50F004AB9AF /* CSSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */; }; B5A5F2681CAEC50F004AB9AF /* CSSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */; }; B5A5F2691CAEC50F004AB9AF /* CSSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */; }; @@ -689,7 +685,6 @@ B59AFF401C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSPersistentStoreCoordinator+Setup.swift"; sourceTree = ""; }; B59FA0AD1CCBAC95007C9BCA /* ICloudStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ICloudStore.swift; sourceTree = ""; }; B5A261201B64BFDB006EB6D3 /* MigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationType.swift; sourceTree = ""; }; - B5A27F851E857C5300203C3E /* TransactionResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionResult.swift; sourceTree = ""; }; B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSelect.swift; sourceTree = ""; }; B5AD60CD1C90141E00F2B2E8 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; }; B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreBridge.swift; sourceTree = ""; }; @@ -1165,7 +1160,6 @@ B5E84EEE1AFF846E0064E85B /* CoreStore+Transaction.swift */, B50392F81C478FF3009900CA /* NSManagedObject+Transaction.swift */, B5E84EF21AFF846E0064E85B /* SaveResult.swift */, - B5A27F851E857C5300203C3E /* TransactionResult.swift */, ); path = Transactions; sourceTree = ""; @@ -1609,7 +1603,6 @@ B549F6731E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */, B509C7F41E54511B0061C547 /* ImportableAttributeType.swift in Sources */, B5E84F0E1AFF847B0064E85B /* Tweak.swift in Sources */, - B5A27F861E857C5300203C3E /* TransactionResult.swift in Sources */, B5E1B5931CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */, B5ECDC291CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, B5E84F121AFF847B0064E85B /* OrderBy.swift in Sources */, @@ -1766,7 +1759,6 @@ B549F6741E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */, B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */, 82BA18B31C4BBD3900A0916E /* ImportableUniqueObject.swift in Sources */, - B5A27F871E857C5300203C3E /* TransactionResult.swift in Sources */, B5E1B5951CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */, B5ECDC2B1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, 82BA18A11C4BBD1D00A0916E /* CoreStore.swift in Sources */, @@ -1923,7 +1915,6 @@ B549F6761E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */, B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */, B5220E1F1D130810009BC71E /* CSListObserver.swift in Sources */, - B5A27F891E857C5300203C3E /* TransactionResult.swift in Sources */, B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */, B52DD1A61BE1F92F00949AFE /* BaseDataTransaction+Importing.swift in Sources */, B5220E1D1D13080A009BC71E /* CSDataStack+Observing.swift in Sources */, @@ -2080,7 +2071,6 @@ B549F6751E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */, B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */, B5E1B5961CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */, - B5A27F881E857C5300203C3E /* TransactionResult.swift in Sources */, B5ECDC2C1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, B56321911BD65216006C9394 /* BaseDataTransaction+Importing.swift in Sources */, B546F95A1C99B17400D5AC55 /* CSCoreStore+Setup.swift in Sources */, diff --git a/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift b/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift index 3f707e8..a1055c1 100644 --- a/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift +++ b/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift @@ -30,47 +30,48 @@ class BaseTestDataTestCase: BaseTestCase { @nonobjc func prepareTestDataForStack(_ stack: DataStack, configurations: [String?] = [nil]) { - stack.beginSynchronous { (transaction) in - - for (configurationIndex, configuration) in configurations.enumerated() { + try! stack.perform( + synchronous: { (transaction) in - let configurationOrdinal = configurationIndex + 1 - if configuration == nil || configuration == "Config1" { + for (configurationIndex, configuration) in configurations.enumerated() { - for idIndex in 1 ... 5 { + let configurationOrdinal = configurationIndex + 1 + if configuration == nil || configuration == "Config1" { - let object = transaction.create(Into(configuration)) - object.testEntityID = NSNumber(value: (configurationOrdinal * 100) + idIndex) - - object.testNumber = NSNumber(value: idIndex) - object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z") - object.testBoolean = NSNumber(value: (idIndex % 2) == 1) - object.testDecimal = NSDecimalNumber(string: "\(idIndex)") - - let string = "\(configuration ?? "nil"):TestEntity1:\(idIndex)" - object.testString = string - object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue) + for idIndex in 1 ... 5 { + + let object = transaction.create(Into(configuration)) + object.testEntityID = NSNumber(value: (configurationOrdinal * 100) + idIndex) + + object.testNumber = NSNumber(value: idIndex) + object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z") + object.testBoolean = NSNumber(value: (idIndex % 2) == 1) + object.testDecimal = NSDecimalNumber(string: "\(idIndex)") + + let string = "\(configuration ?? "nil"):TestEntity1:\(idIndex)" + object.testString = string + object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue) + } } - } - if configuration == nil || configuration == "Config2" { - - for idIndex in 1 ... 5 { + if configuration == nil || configuration == "Config2" { - let object = transaction.create(Into(configuration)) - object.testEntityID = NSNumber(value: (configurationOrdinal * 200) + idIndex) - - object.testNumber = NSNumber(value: idIndex) - object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z") - object.testBoolean = NSNumber(value: (idIndex % 2) == 1) - object.testDecimal = NSDecimalNumber(string: "\(idIndex)") - - let string = "\(configuration ?? "nil"):TestEntity2:\(idIndex)" - object.testString = string - object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue) + for idIndex in 1 ... 5 { + + let object = transaction.create(Into(configuration)) + object.testEntityID = NSNumber(value: (configurationOrdinal * 200) + idIndex) + + object.testNumber = NSNumber(value: idIndex) + object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z") + object.testBoolean = NSNumber(value: (idIndex % 2) == 1) + object.testDecimal = NSDecimalNumber(string: "\(idIndex)") + + let string = "\(configuration ?? "nil"):TestEntity2:\(idIndex)" + object.testString = string + object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue) + } } } } - _ = transaction.commitAndWait() - } + ) } } diff --git a/CoreStoreTests/BridgingTests.m b/CoreStoreTests/BridgingTests.m index 77c18df..cbd2f03 100644 --- a/CoreStoreTests/BridgingTests.m +++ b/CoreStoreTests/BridgingTests.m @@ -212,6 +212,9 @@ CSUnsafeDataTransaction *transaction = [CSCoreStore beginUnsafe]; XCTAssertNotNil(transaction); XCTAssert([transaction isKindOfClass:[CSUnsafeDataTransaction class]]); + NSError *error; + XCTAssertTrue([transaction commitAndWaitWithError:&error]); + XCTAssertNil(error); } { XCTestExpectation *expectation = [self expectationWithDescription:@"sync"]; @@ -219,6 +222,9 @@ XCTAssertNotNil(transaction); XCTAssert([transaction isKindOfClass:[CSSynchronousDataTransaction class]]); + NSError *error; + XCTAssertTrue([transaction commitAndWaitWithError:&error]); + XCTAssertNil(error); [expectation fulfill]; }]; } @@ -228,7 +234,15 @@ XCTAssertNotNil(transaction); XCTAssert([transaction isKindOfClass:[CSAsynchronousDataTransaction class]]); - [expectation fulfill]; + [transaction + commitWithSuccess:^{ + + [expectation fulfill]; + } + failure:^(CSError *error){ + + XCTFail(); + }]; }]; } [self waitForExpectationsWithTimeout:10 handler:nil]; diff --git a/CoreStoreTests/FetchTests.swift b/CoreStoreTests/FetchTests.swift index a03413c..31e56ef 100644 --- a/CoreStoreTests/FetchTests.swift +++ b/CoreStoreTests/FetchTests.swift @@ -72,15 +72,30 @@ final class FetchTests: BaseTestDataTestCase { let fetchExpectation = self.expectation(description: "fetch") var existing1: TestEntity1? - stack.beginSynchronous { (transaction) in + do { - existing1 = transaction.fetchExisting(object) - XCTAssertNotNil(existing1) - XCTAssertEqual(existing1!.objectID, object.objectID) - XCTAssertEqual(existing1!.managedObjectContext, transaction.context) + try stack.perform( + synchronous: { (transaction) in + + existing1 = transaction.fetchExisting(object) + XCTAssertNotNil(existing1) + XCTAssertEqual(existing1!.objectID, object.objectID) + XCTAssertEqual(existing1!.managedObjectContext, transaction.context) + + try transaction.cancel() + } + ) + XCTFail() + } + catch CoreStoreError.userCancelled { fetchExpectation.fulfill() } + catch { + + XCTFail() + } + let existing2 = stack.fetchExisting(existing1!) XCTAssertNotNil(existing2) XCTAssertEqual(existing2!.objectID, object.objectID) @@ -89,14 +104,24 @@ final class FetchTests: BaseTestDataTestCase { do { let fetchExpectation = self.expectation(description: "fetch") - stack.beginAsynchronous { (transaction) in - - let existing1 = transaction.fetchExisting(object) - XCTAssertNotNil(existing1) - XCTAssertEqual(existing1!.objectID, object.objectID) - XCTAssertEqual(existing1!.managedObjectContext, transaction.context) - - DispatchQueue.main.async { + var existing1: TestEntity1? + stack.perform( + asynchronous: { (transaction) in + + existing1 = transaction.fetchExisting(object) + XCTAssertNotNil(existing1) + XCTAssertEqual(existing1!.objectID, object.objectID) + XCTAssertEqual(existing1!.managedObjectContext, transaction.context) + + try transaction.cancel() + }, + success: { + + XCTFail() + }, + failure: { (error) in + + XCTAssertEqual(error, CoreStoreError.userCancelled) let existing2 = stack.fetchExisting(existing1!) XCTAssertNotNil(existing2) @@ -105,7 +130,7 @@ final class FetchTests: BaseTestDataTestCase { fetchExpectation.fulfill() } - } + ) } } self.waitAndCheckExpectations() @@ -165,20 +190,34 @@ final class FetchTests: BaseTestDataTestCase { let fetchExpectation = self.expectation(description: "fetch") var existing1 = [TestEntity1]() - stack.beginSynchronous { (transaction) in + do { - existing1 = transaction.fetchExisting(objects) - XCTAssertEqual( - existing1.map { $0.objectID }, - objects.map { $0.objectID } + try stack.perform( + synchronous: { (transaction) in + + existing1 = transaction.fetchExisting(objects) + XCTAssertEqual( + existing1.map { $0.objectID }, + objects.map { $0.objectID } + ) + for object in existing1 { + + XCTAssertEqual(object.managedObjectContext, transaction.context) + } + + try transaction.cancel() + } ) - for object in existing1 { - - XCTAssertEqual(object.managedObjectContext, transaction.context) - } + XCTFail() + } + catch CoreStoreError.userCancelled { fetchExpectation.fulfill() } + catch { + + XCTFail() + } let existing2 = stack.fetchExisting(existing1) XCTAssertEqual( existing2.map { $0.objectID }, @@ -192,18 +231,28 @@ final class FetchTests: BaseTestDataTestCase { do { let fetchExpectation = self.expectation(description: "fetch") - stack.beginAsynchronous { (transaction) in - - let existing1 = transaction.fetchExisting(objects) - XCTAssertEqual( - existing1.map { $0.objectID }, - objects.map { $0.objectID } - ) - for object in existing1 { + var existing1 = [TestEntity1]() + stack.perform( + asynchronous: { (transaction) in - XCTAssertEqual(object.managedObjectContext, transaction.context) - } - DispatchQueue.main.async { + existing1 = transaction.fetchExisting(objects) + XCTAssertEqual( + existing1.map { $0.objectID }, + objects.map { $0.objectID } + ) + for object in existing1 { + + XCTAssertEqual(object.managedObjectContext, transaction.context) + } + try transaction.cancel() + }, + success: { + + XCTFail() + }, + failure: { (error) in + + XCTAssertEqual(error, CoreStoreError.userCancelled) let existing2 = stack.fetchExisting(existing1) XCTAssertEqual( @@ -216,7 +265,7 @@ final class FetchTests: BaseTestDataTestCase { } fetchExpectation.fulfill() } - } + ) } } self.waitAndCheckExpectations() @@ -380,11 +429,11 @@ final class FetchTests: BaseTestDataTestCase { ] let object = stack.fetchOne(from, fetchClauses) XCTAssertNotNil(object) - XCTAssertEqual(object?.testNumber, 2) // configuration ambiguous let objectID = stack.fetchObjectID(from, fetchClauses) XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) + + // configuration ambiguous, no other behavior should be relied on } do { @@ -394,11 +443,11 @@ final class FetchTests: BaseTestDataTestCase { ] let object = stack.fetchOne(from, fetchClauses) XCTAssertNotNil(object) - XCTAssertEqual(object?.testNumber, 3) // configuration ambiguous let objectID = stack.fetchObjectID(from, fetchClauses) XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) + + // configuration ambiguous, no other behavior should be relied on } do { @@ -563,11 +612,11 @@ final class FetchTests: BaseTestDataTestCase { ] let object = stack.fetchOne(from, fetchClauses) XCTAssertNotNil(object) - XCTAssertEqual(object?.testNumber, 2) // configuration is ambiguous let objectID = stack.fetchObjectID(from, fetchClauses) XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) + + // configuration ambiguous, no other behavior should be relied on } do { @@ -577,11 +626,11 @@ final class FetchTests: BaseTestDataTestCase { ] let object = stack.fetchOne(from, fetchClauses) XCTAssertNotNil(object) - XCTAssertEqual(object?.testNumber, 3) // configuration is ambiguous let objectID = stack.fetchObjectID(from, fetchClauses) XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) + + // configuration ambiguous, no other behavior should be relied on } do { @@ -917,18 +966,12 @@ final class FetchTests: BaseTestDataTestCase { let objects = stack.fetchAll(from, fetchClauses) XCTAssertNotNil(objects) XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - Set((objects ?? []).map { $0.testNumber!.intValue }), - [4, 5] as Set - ) // configuration is ambiguous let objectIDs = stack.fetchObjectIDs(from, fetchClauses) XCTAssertNotNil(objectIDs) XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) + + // configuration ambiguous, no other behavior should be relied on } do { @@ -940,18 +983,12 @@ final class FetchTests: BaseTestDataTestCase { let objects = stack.fetchAll(from, fetchClauses) XCTAssertNotNil(objects) XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - Set((objects ?? []).map { $0.testNumber!.intValue }), - [1, 2] as Set - ) // configuration is ambiguous let objectIDs = stack.fetchObjectIDs(from, fetchClauses) XCTAssertNotNil(objectIDs) XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) + + // configuration ambiguous, no other behavior should be relied on } do { @@ -1166,18 +1203,12 @@ final class FetchTests: BaseTestDataTestCase { let objects = stack.fetchAll(from, fetchClauses) XCTAssertNotNil(objects) XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - Set((objects ?? []).map { $0.testNumber!.intValue }), - [4, 5] as Set - ) // configuration is ambiguous let objectIDs = stack.fetchObjectIDs(from, fetchClauses) XCTAssertNotNil(objectIDs) XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) + + // configuration ambiguous, no other behavior should be relied on } do { @@ -1189,18 +1220,12 @@ final class FetchTests: BaseTestDataTestCase { let objects = stack.fetchAll(from, fetchClauses) XCTAssertNotNil(objects) XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - Set((objects ?? []).map { $0.testNumber!.intValue }), - [1, 2] as Set - ) // configuration is ambiguous let objectIDs = stack.fetchObjectIDs(from, fetchClauses) XCTAssertNotNil(objectIDs) XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) + + // configuration ambiguous, no other behavior should be relied on } do { @@ -1764,135 +1789,143 @@ final class FetchTests: BaseTestDataTestCase { self.prepareStack(configurations: configurations) { (stack) in self.prepareTestDataForStack(stack, configurations: configurations) - - stack.beginSynchronous { (transaction) in + _ = try? stack.perform( + synchronous: { (transaction) in - let from = From() - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "nil:TestEntity1:2") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "nil:TestEntity1:3") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNil(object) - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNil(objectID) - } - } - stack.beginSynchronous { (transaction) in - - let from = From(nil) - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "nil:TestEntity1:2") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "nil:TestEntity1:3") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNil(object) - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNil(objectID) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config1") - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = self.expectLogger([.logWarning]) { + let from = From() + do { - transaction.fetchOne(from, fetchClauses) - } - XCTAssertNil(object) - - let objectID = self.expectLogger([.logWarning]) { + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "nil:TestEntity1:2") - transaction.fetchObjectID(from, fetchClauses) + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) } - XCTAssertNil(objectID) + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "nil:TestEntity1:3") + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNil(object) + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNil(objectID) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = self.expectLogger([.logWarning]) { + let from = From(nil) + do { - transaction.fetchOne(from, fetchClauses) - } - XCTAssertNil(object) - - let objectID = self.expectLogger([.logWarning]) { + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "nil:TestEntity1:2") - transaction.fetchObjectID(from, fetchClauses) + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) } - XCTAssertNil(objectID) + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "nil:TestEntity1:3") + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNil(object) + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNil(objectID) + } + try transaction.cancel() } - } + ) + _ = try? stack.perform( + synchronous: { (transaction) in + + let from = From("Config1") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = self.expectLogger([.logWarning]) { + + transaction.fetchOne(from, fetchClauses) + } + XCTAssertNil(object) + + let objectID = self.expectLogger([.logWarning]) { + + transaction.fetchObjectID(from, fetchClauses) + } + XCTAssertNil(objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = self.expectLogger([.logWarning]) { + + transaction.fetchOne(from, fetchClauses) + } + XCTAssertNil(object) + + let objectID = self.expectLogger([.logWarning]) { + + transaction.fetchObjectID(from, fetchClauses) + } + XCTAssertNil(objectID) + } + try transaction.cancel() + } + ) } } @@ -1903,179 +1936,190 @@ final class FetchTests: BaseTestDataTestCase { self.prepareStack(configurations: configurations) { (stack) in self.prepareTestDataForStack(stack, configurations: configurations) - - stack.beginSynchronous { (transaction) in - - let from = From() - do { + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testNumber, 2) // configuration ambiguous - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testNumber, 3) // configuration - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNil(object) - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNil(objectID) - } - } - stack.beginSynchronous { (transaction) in - - let from = From(nil) - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "nil:TestEntity1:2") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "nil:TestEntity1:3") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNil(object) - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNil(objectID) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config1") - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "Config1:TestEntity1:2") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "Config1:TestEntity1:3") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNil(object) - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNil(objectID) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config2") - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = self.expectLogger([.logWarning]) { + let from = From() + do { - transaction.fetchOne(from, fetchClauses) - } - XCTAssertNil(object) - - let objectID = self.expectLogger([.logWarning]) { + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testNumber, 2) // configuration ambiguous - transaction.fetchObjectID(from, fetchClauses) + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) } - XCTAssertNil(objectID) + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testNumber, 3) // configuration + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNil(object) + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNil(objectID) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = self.expectLogger([.logWarning]) { + let from = From(nil) + do { - transaction.fetchOne(from, fetchClauses) - } - XCTAssertNil(object) - - let objectID = self.expectLogger([.logWarning]) { + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "nil:TestEntity1:2") - transaction.fetchObjectID(from, fetchClauses) + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) } - XCTAssertNil(objectID) + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "nil:TestEntity1:3") + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNil(object) + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNil(objectID) + } + try transaction.cancel() } - } + ) + _ = try? stack.perform( + synchronous: { (transaction) in + + let from = From("Config1") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "Config1:TestEntity1:2") + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "Config1:TestEntity1:3") + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNil(object) + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNil(objectID) + } + try transaction.cancel() + } + ) + _ = try? stack.perform( + synchronous: { (transaction) in + + let from = From("Config2") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = self.expectLogger([.logWarning]) { + + transaction.fetchOne(from, fetchClauses) + } + XCTAssertNil(object) + + let objectID = self.expectLogger([.logWarning]) { + + transaction.fetchObjectID(from, fetchClauses) + } + XCTAssertNil(objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = self.expectLogger([.logWarning]) { + + transaction.fetchOne(from, fetchClauses) + } + XCTAssertNil(object) + + let objectID = self.expectLogger([.logWarning]) { + + transaction.fetchObjectID(from, fetchClauses) + } + XCTAssertNil(objectID) + } + try transaction.cancel() + } + ) } } @@ -2086,139 +2130,147 @@ final class FetchTests: BaseTestDataTestCase { self.prepareStack(configurations: configurations) { (stack) in self.prepareTestDataForStack(stack, configurations: configurations) - - stack.beginSynchronous { (transaction) in - - let from = From(nil, "Config1") - do { + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testNumber, 2) // configuration is ambiguous - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) + let from = From(nil, "Config1") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testNumber, 2) // configuration is ambiguous + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testNumber, 3) // configuration is ambiguous + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNil(object) + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNil(objectID) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testNumber, 3) // configuration is ambiguous - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) + let from = From(nil, "Config2") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "nil:TestEntity1:2") + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "nil:TestEntity1:3") + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNil(object) + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNil(objectID) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNil(object) - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNil(objectID) + let from = From("Config1", "Config2") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "Config1:TestEntity1:2") + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testString, "Config1:TestEntity1:3") + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNotNil(objectID) + XCTAssertEqual(objectID, object?.objectID) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let object = transaction.fetchOne(from, fetchClauses) + XCTAssertNil(object) + + let objectID = transaction.fetchObjectID(from, fetchClauses) + XCTAssertNil(objectID) + } + try transaction.cancel() } - } - stack.beginSynchronous { (transaction) in - - let from = From(nil, "Config2") - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "nil:TestEntity1:2") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "nil:TestEntity1:3") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNil(object) - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNil(objectID) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config1", "Config2") - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "Config1:TestEntity1:2") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testString, "Config1:TestEntity1:3") - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNotNil(objectID) - XCTAssertEqual(objectID, object?.objectID) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let object = transaction.fetchOne(from, fetchClauses) - XCTAssertNil(object) - - let objectID = transaction.fetchObjectID(from, fetchClauses) - XCTAssertNil(objectID) - } - } + ) } } @@ -2229,205 +2281,213 @@ final class FetchTests: BaseTestDataTestCase { self.prepareStack(configurations: configurations) { (stack) in self.prepareTestDataForStack(stack, configurations: configurations) - - stack.beginSynchronous { (transaction) in - - let from = From() - do { + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "nil:TestEntity1:2", - "nil:TestEntity1:3", - "nil:TestEntity1:4" + let from = From() + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "nil:TestEntity1:4", - "nil:TestEntity1:3", - "nil:TestEntity1:2" + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 3) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "nil:TestEntity1:2", + "nil:TestEntity1:3", + "nil:TestEntity1:4" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 3) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 0) - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From(nil) - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "nil:TestEntity1:2", - "nil:TestEntity1:3", - "nil:TestEntity1:4" + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 3) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "nil:TestEntity1:4", + "nil:TestEntity1:3", + "nil:TestEntity1:2" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 3) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 0) + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 0) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "nil:TestEntity1:4", - "nil:TestEntity1:3", - "nil:TestEntity1:2" + let from = From(nil) + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 3) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "nil:TestEntity1:2", + "nil:TestEntity1:3", + "nil:TestEntity1:4" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 3) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 3) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "nil:TestEntity1:4", + "nil:TestEntity1:3", + "nil:TestEntity1:2" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 3) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 0) + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 0) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 0) - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 0) + let from = From("Config1") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = self.expectLogger([.logWarning]) { + + transaction.fetchAll(from, fetchClauses) + } + XCTAssertNil(objects) + + let objectIDs = self.expectLogger([.logWarning]) { + + transaction.fetchObjectIDs(from, fetchClauses) + } + XCTAssertNil(objectIDs) + } + do { + + let fetchClauses: [FetchClause] = [ + Where(#keyPath(TestEntity1.testNumber), isEqualTo: 0), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = self.expectLogger([.logWarning]) { + + transaction.fetchAll(from, fetchClauses) + } + XCTAssertNil(objects) + + let objectIDs = self.expectLogger([.logWarning]) { + + transaction.fetchObjectIDs(from, fetchClauses) + } + XCTAssertNil(objectIDs) + } + do { + + let fetchClauses: [FetchClause] = [ + Where(#keyPath(TestEntity1.testNumber), isEqualTo: nil), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = self.expectLogger([.logWarning]) { + + transaction.fetchAll(from, fetchClauses) + } + XCTAssertNil(objects) + + let objectIDs = self.expectLogger([.logWarning]) { + + transaction.fetchObjectIDs(from, fetchClauses) + } + XCTAssertNil(objectIDs) + } + try transaction.cancel() } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config1") - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = self.expectLogger([.logWarning]) { - - transaction.fetchAll(from, fetchClauses) - } - XCTAssertNil(objects) - - let objectIDs = self.expectLogger([.logWarning]) { - - transaction.fetchObjectIDs(from, fetchClauses) - } - XCTAssertNil(objectIDs) - } - do { - - let fetchClauses: [FetchClause] = [ - Where(#keyPath(TestEntity1.testNumber), isEqualTo: 0), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = self.expectLogger([.logWarning]) { - - transaction.fetchAll(from, fetchClauses) - } - XCTAssertNil(objects) - - let objectIDs = self.expectLogger([.logWarning]) { - - transaction.fetchObjectIDs(from, fetchClauses) - } - XCTAssertNil(objectIDs) - } - do { - - let fetchClauses: [FetchClause] = [ - Where(#keyPath(TestEntity1.testNumber), isEqualTo: nil), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = self.expectLogger([.logWarning]) { - - transaction.fetchAll(from, fetchClauses) - } - XCTAssertNil(objects) - - let objectIDs = self.expectLogger([.logWarning]) { - - transaction.fetchObjectIDs(from, fetchClauses) - } - XCTAssertNil(objectIDs) - } - } + ) } } @@ -2438,245 +2498,256 @@ final class FetchTests: BaseTestDataTestCase { self.prepareStack(configurations: configurations) { (stack) in self.prepareTestDataForStack(stack, configurations: configurations) - - stack.beginSynchronous { (transaction) in - - let from = From() - do { + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - Set((objects ?? []).map { $0.testNumber!.intValue }), - [4, 5] as Set - ) // configuration is ambiguous - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - Set((objects ?? []).map { $0.testNumber!.intValue }), - [1, 2] as Set - ) // configuration is ambiguous - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 0) - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From(nil) - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 2) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "nil:TestEntity1:4", - "nil:TestEntity1:5" - ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 2) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 2) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "nil:TestEntity1:2", - "nil:TestEntity1:1" - ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 2) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 0) - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config1") - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 2) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "Config1:TestEntity1:4", - "Config1:TestEntity1:5" - ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 2) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 2) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "Config1:TestEntity1:2", - "Config1:TestEntity1:1" - ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 2) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 0) - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config2") - do { - - let fetchClauses: [FetchClause] = [ - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = self.expectLogger([.logWarning]) { + let from = From() + do { - transaction.fetchAll(from, fetchClauses) - } - XCTAssertNil(objects) - - let objectIDs = self.expectLogger([.logWarning]) { + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 3) + XCTAssertEqual( + Set((objects ?? []).map { $0.testNumber!.intValue }), + [4, 5] as Set + ) // configuration is ambiguous - transaction.fetchObjectIDs(from, fetchClauses) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 3) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) } - XCTAssertNil(objectIDs) + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 3) + XCTAssertEqual( + Set((objects ?? []).map { $0.testNumber!.intValue }), + [1, 2] as Set + ) // configuration is ambiguous + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 3) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 0) + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 0) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = self.expectLogger([.logWarning]) { + let from = From(nil) + do { - transaction.fetchAll(from, fetchClauses) + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 2) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "nil:TestEntity1:4", + "nil:TestEntity1:5" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 2) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) } - XCTAssertNil(objects) - - let objectIDs = self.expectLogger([.logWarning]) { + do { - transaction.fetchObjectIDs(from, fetchClauses) + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 2) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "nil:TestEntity1:2", + "nil:TestEntity1:1" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 2) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) } - XCTAssertNil(objectIDs) + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 0) + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 0) + } + try transaction.cancel() } - } + ) + _ = try? stack.perform( + synchronous: { (transaction) in + + let from = From("Config1") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 2) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "Config1:TestEntity1:4", + "Config1:TestEntity1:5" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 2) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 2) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "Config1:TestEntity1:2", + "Config1:TestEntity1:1" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 2) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 0) + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 0) + } + try transaction.cancel() + } + ) + _ = try? stack.perform( + synchronous: { (transaction) in + + let from = From("Config2") + do { + + let fetchClauses: [FetchClause] = [ + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = self.expectLogger([.logWarning]) { + + transaction.fetchAll(from, fetchClauses) + } + XCTAssertNil(objects) + + let objectIDs = self.expectLogger([.logWarning]) { + + transaction.fetchObjectIDs(from, fetchClauses) + } + XCTAssertNil(objectIDs) + } + do { + + let fetchClauses: [FetchClause] = [ + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = self.expectLogger([.logWarning]) { + + transaction.fetchAll(from, fetchClauses) + } + XCTAssertNil(objects) + + let objectIDs = self.expectLogger([.logWarning]) { + + transaction.fetchObjectIDs(from, fetchClauses) + } + XCTAssertNil(objectIDs) + } + try transaction.cancel() + } + ) } } @@ -2687,207 +2758,215 @@ final class FetchTests: BaseTestDataTestCase { self.prepareStack(configurations: configurations) { (stack) in self.prepareTestDataForStack(stack, configurations: configurations) - - stack.beginSynchronous { (transaction) in - - let from = From(nil, "Config1") - do { + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - Set((objects ?? []).map { $0.testNumber!.intValue }), - [4, 5] as Set - ) // configuration is ambiguous - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 3) - XCTAssertEqual( - Set((objects ?? []).map { $0.testNumber!.intValue }), - [1, 2] as Set - ) // configuration is ambiguous - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 3) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 0) - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From(nil, "Config2") - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 2) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "nil:TestEntity1:4", - "nil:TestEntity1:5" + let from = From(nil, "Config1") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 2) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 2) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "nil:TestEntity1:2", - "nil:TestEntity1:1" + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 3) + XCTAssertEqual( + Set((objects ?? []).map { $0.testNumber!.intValue }), + [4, 5] as Set + ) // configuration is ambiguous + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 3) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 2) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) - } - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 0) - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config1", "Config2") - do { - - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 2) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "Config1:TestEntity1:4", - "Config1:TestEntity1:5" + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 3) + XCTAssertEqual( + Set((objects ?? []).map { $0.testNumber!.intValue }), + [1, 2] as Set + ) // configuration is ambiguous + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 3) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 2) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 0) + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 0) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 2) - XCTAssertEqual( - (objects ?? []).map { $0.testString ?? "" }, - [ - "Config1:TestEntity1:2", - "Config1:TestEntity1:1" + let from = From(nil, "Config2") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } ] - ) - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 2) - XCTAssertEqual( - (objectIDs ?? []), - (objects ?? []).map { $0.objectID } - ) + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 2) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "nil:TestEntity1:4", + "nil:TestEntity1:5" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 2) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 2) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "nil:TestEntity1:2", + "nil:TestEntity1:1" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 2) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 0) + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 0) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let fetchClauses: [FetchClause] = [ - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ] - let objects = transaction.fetchAll(from, fetchClauses) - XCTAssertNotNil(objects) - XCTAssertEqual(objects?.count, 0) - - let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) - XCTAssertNotNil(objectIDs) - XCTAssertEqual(objectIDs?.count, 0) + let from = From("Config1", "Config2") + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 2) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "Config1:TestEntity1:4", + "Config1:TestEntity1:5" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 2) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 2) + XCTAssertEqual( + (objects ?? []).map { $0.testString ?? "" }, + [ + "Config1:TestEntity1:2", + "Config1:TestEntity1:1" + ] + ) + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 2) + XCTAssertEqual( + (objectIDs ?? []), + (objects ?? []).map { $0.objectID } + ) + } + do { + + let fetchClauses: [FetchClause] = [ + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ] + let objects = transaction.fetchAll(from, fetchClauses) + XCTAssertNotNil(objects) + XCTAssertEqual(objects?.count, 0) + + let objectIDs = transaction.fetchObjectIDs(from, fetchClauses) + XCTAssertNotNil(objectIDs) + XCTAssertEqual(objectIDs?.count, 0) + } + try transaction.cancel() } - } + ) } } @@ -2898,119 +2977,127 @@ final class FetchTests: BaseTestDataTestCase { self.prepareStack(configurations: configurations) { (stack) in self.prepareTestDataForStack(stack, configurations: configurations) - - stack.beginSynchronous { (transaction) in - - let from = From() - do { + _ = try? stack.perform( + synchronous: { (transaction) in - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 3) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 3) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From(nil) - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 3) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 3) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config1") - do { - - let count = self.expectLogger([.logWarning]) { + let from = From() + do { - transaction.fetchCount( + let count = transaction.fetchCount( from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 3) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 3) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 0) } - XCTAssertNil(count) + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let count = self.expectLogger([.logWarning]) { + let from = From(nil) + do { - transaction.fetchCount( + let count = transaction.fetchCount( from, - Where(#keyPath(TestEntity1.testNumber), isEqualTo: 0), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + Where("%K > %@", #keyPath(TestEntity1.testNumber), 1), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 3) } - XCTAssertNil(count) + do { + + let count = transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 3) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 0) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let count = self.expectLogger([.logWarning]) { + let from = From("Config1") + do { - transaction.fetchCount( - from, - Where(#keyPath(TestEntity1.testNumber), isEqualTo: nil), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) - ) + let count = self.expectLogger([.logWarning]) { + + transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ) + } + XCTAssertNil(count) } - XCTAssertNil(count) + do { + + let count = self.expectLogger([.logWarning]) { + + transaction.fetchCount( + from, + Where(#keyPath(TestEntity1.testNumber), isEqualTo: 0), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ) + } + XCTAssertNil(count) + } + do { + + let count = self.expectLogger([.logWarning]) { + + transaction.fetchCount( + from, + Where(#keyPath(TestEntity1.testNumber), isEqualTo: nil), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ) + } + XCTAssertNil(count) + } + try transaction.cancel() } - } + ) } } @@ -3021,155 +3108,166 @@ final class FetchTests: BaseTestDataTestCase { self.prepareStack(configurations: configurations) { (stack) in self.prepareTestDataForStack(stack, configurations: configurations) - - stack.beginSynchronous { (transaction) in - - let from = From() - do { + _ = try? stack.perform( + synchronous: { (transaction) in - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 3) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 3) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From(nil) - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 2) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 2) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config1") - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 2) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 2) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config2") - do { - - let count = self.expectLogger([.logWarning]) { + let from = From() + do { - transaction.fetchCount( + let count = transaction.fetchCount( from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 3) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 3) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 0) } - XCTAssertNil(count) + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let count = self.expectLogger([.logWarning]) { + let from = From(nil) + do { - transaction.fetchCount( + let count = transaction.fetchCount( from, - Where(#keyPath(TestEntity1.testNumber), isEqualTo: 0), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 2) } - XCTAssertNil(count) + do { + + let count = transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 2) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 0) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let count = self.expectLogger([.logWarning]) { + let from = From("Config1") + do { - transaction.fetchCount( + let count = transaction.fetchCount( from, - Where(#keyPath(TestEntity1.testNumber), isEqualTo: nil), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 2) } - XCTAssertNil(count) + do { + + let count = transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 2) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 0) + } + try transaction.cancel() } - } + ) + _ = try? stack.perform( + synchronous: { (transaction) in + + let from = From("Config2") + do { + + let count = self.expectLogger([.logWarning]) { + + transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 4), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ) + } + XCTAssertNil(count) + } + do { + + let count = self.expectLogger([.logWarning]) { + + transaction.fetchCount( + from, + Where(#keyPath(TestEntity1.testNumber), isEqualTo: 0), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ) + } + XCTAssertNil(count) + } + do { + + let count = self.expectLogger([.logWarning]) { + + transaction.fetchCount( + from, + Where(#keyPath(TestEntity1.testNumber), isEqualTo: nil), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))) + ) + } + XCTAssertNil(count) + } + try transaction.cancel() + } + ) } } @@ -3180,115 +3278,123 @@ final class FetchTests: BaseTestDataTestCase { self.prepareStack(configurations: configurations) { (stack) in self.prepareTestDataForStack(stack, configurations: configurations) - - stack.beginSynchronous { (transaction) in - - let from = From(nil, "Config1") - do { + _ = try? stack.perform( + synchronous: { (transaction) in - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 3) + let from = From(nil, "Config1") + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 3) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 3) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 0) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let count = transaction.fetchCount( - from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 3) + let from = From(nil, "Config2") + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 2) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 2) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 0) + } + try transaction.cancel() } - do { + ) + _ = try? stack.perform( + synchronous: { (transaction) in - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 0) + let from = From("Config1", "Config2") + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 2) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), + OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), + Tweak { $0.fetchLimit = 3 } + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 2) + } + do { + + let count = transaction.fetchCount( + from, + Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), + OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) + ) + XCTAssertNotNil(count) + XCTAssertEqual(count, 0) + } + try transaction.cancel() } - } - stack.beginSynchronous { (transaction) in - - let from = From(nil, "Config2") - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 2) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 2) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 0) - } - } - stack.beginSynchronous { (transaction) in - - let from = From("Config1", "Config2") - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 2) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K < %@", #keyPath(TestEntity1.testNumber), 3), - OrderBy(.descending(#keyPath(TestEntity1.testEntityID))), - Tweak { $0.fetchLimit = 3 } - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 2) - } - do { - - let count = transaction.fetchCount( - from, - Where("%K > %@", #keyPath(TestEntity1.testNumber), 5), - OrderBy(.ascending(#keyPath(TestEntity1.testEntityID))) - ) - XCTAssertNotNil(count) - XCTAssertEqual(count, 0) - } - } + ) } } } diff --git a/CoreStoreTests/ImportTests.swift b/CoreStoreTests/ImportTests.swift index 035a4c4..f2e221e 100644 --- a/CoreStoreTests/ImportTests.swift +++ b/CoreStoreTests/ImportTests.swift @@ -77,30 +77,33 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - stack.beginSynchronous { (transaction) in + do { - do { - - let object = try transaction.importObject( - Into(), - source: [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 1), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:1", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!, - "skip_insert": "" - ] - ) - XCTAssertNil(object) - XCTAssertEqual(transaction.fetchCount(From()), 0) - } - catch { - - XCTFail() - } + try stack.perform( + synchronous: { (transaction) in + + let object = try transaction.importObject( + Into(), + source: [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 1), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:1", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!, + "skip_insert": "" + ] + ) + XCTAssertNil(object) + XCTAssertEqual(transaction.fetchCount(From()), 0) + } + ) } + catch { + + XCTFail() + } + XCTAssertEqual(stack.fetchCount(From()), 0) } } @@ -109,46 +112,50 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - stack.beginSynchronous { (transaction) in + do { let errorExpectation = self.expectation(description: "error") - do { - - let _ = try transaction.importObject( - Into(), - source: [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 1), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:1", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!, - "throw_on_insert": "" - ] - ) - XCTFail() - } - catch _ as TestInsertError { - - errorExpectation.fulfill() - XCTAssertEqual(transaction.fetchCount(From()), 1) - - let object = transaction.fetchOne(From()) - XCTAssertNotNil(object) - XCTAssertNil(object?.testEntityID) - XCTAssertNil(object?.testBoolean) - XCTAssertNil(object?.testNumber) - XCTAssertNil(object?.testDecimal) - XCTAssertNil(object?.testString) - XCTAssertNil(object?.testData) - XCTAssertNil(object?.testDate) - } - catch { - - XCTFail() - } + try stack.perform( + synchronous: { (transaction) in + + do { + + _ = try transaction.importObject( + Into(), + source: [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 1), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:1", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!, + "throw_on_insert": "" + ] + ) + XCTFail() + } + catch _ as TestInsertError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From()), 1) + + let object = transaction.fetchOne(From()) + XCTAssertNotNil(object) + XCTAssertNil(object?.testEntityID) + XCTAssertNil(object?.testBoolean) + XCTAssertNil(object?.testNumber) + XCTAssertNil(object?.testDecimal) + XCTAssertNil(object?.testString) + XCTAssertNil(object?.testData) + XCTAssertNil(object?.testDate) + } + } + ) self.checkExpectationsImmediately() - transaction.context.reset() + } + catch { + + XCTFail() } } } @@ -158,56 +165,57 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - stack.beginSynchronous { (transaction) in + do { - do { - - let object = try transaction.importObject( - Into(), - source: [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 1), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:1", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")! - ] - ) - XCTAssertNotNil(object) - XCTAssertEqual(transaction.fetchCount(From()), 1) - XCTAssertNil(object?.testEntityID) - XCTAssertEqual(object?.testBoolean, NSNumber(value: true)) - XCTAssertEqual(object?.testNumber, NSNumber(value: 1)) - XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1")) - XCTAssertEqual(object?.testString, "nil:TestEntity1:1") - XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!) - XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!) - - try transaction.importObject( - object!, - source: [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 2), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "2"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:2", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-02T00:00:00Z")! - ] - ) - XCTAssertEqual(transaction.fetchCount(From()), 1) - XCTAssertNil(object?.testEntityID) - XCTAssertEqual(object?.testBoolean, NSNumber(value: false)) - XCTAssertEqual(object?.testNumber, NSNumber(value: 2)) - XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "2")) - XCTAssertEqual(object?.testString, "nil:TestEntity1:2") - XCTAssertEqual(object?.testData, ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!) - XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-02T00:00:00Z")!) - } - catch { - - XCTFail() - } - transaction.context.reset() + try stack.perform( + synchronous: { (transaction) in + + let object = try transaction.importObject( + Into(), + source: [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 1), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:1", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")! + ] + ) + XCTAssertNotNil(object) + XCTAssertEqual(transaction.fetchCount(From()), 1) + XCTAssertNil(object?.testEntityID) + XCTAssertEqual(object?.testBoolean, NSNumber(value: true)) + XCTAssertEqual(object?.testNumber, NSNumber(value: 1)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:1") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!) + + try transaction.importObject( + object!, + source: [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 2), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "2"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:2", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-02T00:00:00Z")! + ] + ) + XCTAssertEqual(transaction.fetchCount(From()), 1) + XCTAssertNil(object?.testEntityID) + XCTAssertEqual(object?.testBoolean, NSNumber(value: false)) + XCTAssertEqual(object?.testNumber, NSNumber(value: 2)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "2")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:2") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-02T00:00:00Z")!) + } + ) + } + catch { + + XCTFail() } } } @@ -217,51 +225,52 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - stack.beginSynchronous { (transaction) in + do { - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 1), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:1", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!, - "skip_insert": "" - ], - [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 2), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "2"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:2", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-02T00:00:00Z")! + try stack.perform( + synchronous: { (transaction) in + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 1), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:1", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!, + "skip_insert": "" + ], + [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 2), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "2"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:2", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-02T00:00:00Z")! + ] ] - ] - let objects = try transaction.importObjects( - Into(), - sourceArray: sourceArray - ) - XCTAssertEqual(objects.count, 1) - XCTAssertEqual(transaction.fetchCount(From()), 1) - - let object = objects[0] - let dictionary = sourceArray[1] - XCTAssertNil(object.testEntityID) - XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) - XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) - XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) - XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) - XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) - XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) - } - catch { - - XCTFail() - } - transaction.context.reset() + let objects = try transaction.importObjects( + Into(), + sourceArray: sourceArray + ) + XCTAssertEqual(objects.count, 1) + XCTAssertEqual(transaction.fetchCount(From()), 1) + + let object = objects[0] + let dictionary = sourceArray[1] + XCTAssertNil(object.testEntityID) + XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) + XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) + XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) + } + ) + } + catch { + + XCTFail() } } } @@ -271,57 +280,61 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - stack.beginSynchronous { (transaction) in + do { let errorExpectation = self.expectation(description: "error") - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 1), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:1", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!, - "throw_on_insert": "" - ], - [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 2), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "2"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:2", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-02T00:00:00Z")! - ] - ] - let _ = try transaction.importObjects( - Into(), - sourceArray: sourceArray - ) - XCTFail() - } - catch _ as TestInsertError { - - errorExpectation.fulfill() - XCTAssertEqual(transaction.fetchCount(From()), 1) - - let object = transaction.fetchOne(From()) - XCTAssertNotNil(object) - XCTAssertNil(object?.testEntityID) - XCTAssertNil(object?.testBoolean) - XCTAssertNil(object?.testNumber) - XCTAssertNil(object?.testDecimal) - XCTAssertNil(object?.testString) - XCTAssertNil(object?.testData) - XCTAssertNil(object?.testDate) - } - catch { - - XCTFail() - } + try stack.perform( + synchronous: { (transaction) in + + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 1), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:1", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!, + "throw_on_insert": "" + ], + [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 2), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "2"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:2", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-02T00:00:00Z")! + ] + ] + let _ = try transaction.importObjects( + Into(), + sourceArray: sourceArray + ) + XCTFail() + } + catch _ as TestInsertError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From()), 1) + + let object = transaction.fetchOne(From()) + XCTAssertNotNil(object) + XCTAssertNil(object?.testEntityID) + XCTAssertNil(object?.testBoolean) + XCTAssertNil(object?.testNumber) + XCTAssertNil(object?.testDecimal) + XCTAssertNil(object?.testString) + XCTAssertNil(object?.testData) + XCTAssertNil(object?.testDate) + } + } + ) self.checkExpectationsImmediately() - transaction.context.reset() + } + catch { + + XCTFail() } } } @@ -331,54 +344,55 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - stack.beginSynchronous { (transaction) in + do { - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 1), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:1", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")! - ], - [ - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 2), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "2"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:2", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-02T00:00:00Z")! + try stack.perform( + synchronous: { (transaction) in + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 1), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "1"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:1", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-01T00:00:00Z")! + ], + [ + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 2), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "2"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:2", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:2" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-02T00:00:00Z")! + ] ] - ] - let objects = try transaction.importObjects( - Into(), - sourceArray: sourceArray - ) - XCTAssertEqual(objects.count, sourceArray.count) - XCTAssertEqual(transaction.fetchCount(From()), 2) - - for i in 0 ..< sourceArray.count { + let objects = try transaction.importObjects( + Into(), + sourceArray: sourceArray + ) + XCTAssertEqual(objects.count, sourceArray.count) + XCTAssertEqual(transaction.fetchCount(From()), 2) - let object = objects[i] - let dictionary = sourceArray[i] - - XCTAssertNil(object.testEntityID) - XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) - XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) - XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) - XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) - XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) - XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) + for i in 0 ..< sourceArray.count { + + let object = objects[i] + let dictionary = sourceArray[i] + + XCTAssertNil(object.testEntityID) + XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) + XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) + XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) + } } - } - catch { - - XCTFail() - } - transaction.context.reset() + ) + } + catch { + + XCTFail() } } } @@ -389,66 +403,66 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in self.prepareTestDataForStack(stack) - - stack.beginSynchronous { (transaction) in + do { - do { - - let object = try transaction.importUniqueObject( - Into(), - source: [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, - "skip_insert": "" - ] - ) - XCTAssertNil(object) - XCTAssertEqual(transaction.fetchCount(From()), 5) - } - catch { - - XCTFail() - } - do { - - let object = try transaction.importUniqueObject( - Into(), - source: [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 105), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, - "skip_update": "" - ] - ) - XCTAssertNil(object) - XCTAssertEqual(transaction.fetchCount(From()), 5) - - let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) - XCTAssertNotNil(existingObjects) - XCTAssertEqual(existingObjects?.count, 1) - - let existingObject = existingObjects?[0] - XCTAssertEqual(existingObject?.testEntityID, NSNumber(value: 105)) - XCTAssertEqual(existingObject?.testBoolean, NSNumber(value: true)) - XCTAssertEqual(existingObject?.testNumber, NSNumber(value: 5)) - XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5")) - XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5") - XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).data(using: String.Encoding.utf8.rawValue)!) - XCTAssertEqual(existingObject?.testDate, self.dateFormatter.date(from: "2000-01-05T00:00:00Z")!) - } - catch { - - XCTFail() - } + try stack.perform( + synchronous: { (transaction) in + + do { + + let object = try transaction.importUniqueObject( + Into(), + source: [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, + "skip_insert": "" + ] + ) + XCTAssertNil(object) + XCTAssertEqual(transaction.fetchCount(From()), 5) + } + do { + + let object = try transaction.importUniqueObject( + Into(), + source: [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 105), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, + "skip_update": "" + ] + ) + XCTAssertNil(object) + XCTAssertEqual(transaction.fetchCount(From()), 5) + + let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) + + let existingObject = existingObjects?[0] + XCTAssertEqual(existingObject?.testEntityID, NSNumber(value: 105)) + XCTAssertEqual(existingObject?.testBoolean, NSNumber(value: true)) + XCTAssertEqual(existingObject?.testNumber, NSNumber(value: 5)) + XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5")) + XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5") + XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).data(using: String.Encoding.utf8.rawValue)!) + XCTAssertEqual(existingObject?.testDate, self.dateFormatter.date(from: "2000-01-05T00:00:00Z")!) + } + } + ) + } + catch { + + XCTFail() } } } @@ -459,54 +473,54 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in self.prepareTestDataForStack(stack) - - stack.beginSynchronous { (transaction) in + do { - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")! - ], - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 7), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:7", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! + try stack.perform( + synchronous: { (transaction) in + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")! + ], + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 7), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:7", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! + ] ] - ] - let objects = try transaction.importUniqueObjects( - Into(), - sourceArray: sourceArray - ) - - XCTAssertEqual(objects.count, 1) - XCTAssertEqual(transaction.fetchCount(From()), 6) - - let object = objects[0] - let dictionary = sourceArray[1] - XCTAssertEqual(object.testEntityID, dictionary[(#keyPath(TestEntity1.testEntityID))] as? NSNumber) - XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) - XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) - XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) - XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) - XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) - XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) - } - catch { - - XCTFail() - } - transaction.context.reset() + let objects = try transaction.importUniqueObjects( + Into(), + sourceArray: sourceArray + ) + + XCTAssertEqual(objects.count, 1) + XCTAssertEqual(transaction.fetchCount(From()), 6) + + let object = objects[0] + let dictionary = sourceArray[1] + XCTAssertEqual(object.testEntityID, dictionary[(#keyPath(TestEntity1.testEntityID))] as? NSNumber) + XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) + XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) + XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) + } + ) + } + catch { + + XCTFail() } } } @@ -517,55 +531,55 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in self.prepareTestDataForStack(stack) - - stack.beginSynchronous { (transaction) in - - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")! - ], - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 107), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 7), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:7", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! + do { + + try stack.perform( + synchronous: { (transaction) in + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")! + ], + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 107), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 7), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:7", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! + ] ] - ] - - let objects = try transaction.importUniqueObjects( - Into(), - sourceArray: sourceArray - ) - - XCTAssertEqual(objects.count, 2) - - zip(objects, sourceArray) - .forEach { object, dictionary in - XCTAssertEqual(object.testEntityID, dictionary[(#keyPath(TestEntity1.testEntityID))] as? NSNumber) - XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) - XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) - XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) - XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) - XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) - XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) + + let objects = try transaction.importUniqueObjects( + Into(), + sourceArray: sourceArray + ) + + XCTAssertEqual(objects.count, 2) + + zip(objects, sourceArray) + .forEach { object, dictionary in + XCTAssertEqual(object.testEntityID, dictionary[(#keyPath(TestEntity1.testEntityID))] as? NSNumber) + XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) + XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) + XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) } - } - catch { - - XCTFail() - } - transaction.context.reset() + } + ) + } + catch { + + XCTFail() } } } @@ -576,96 +590,94 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in self.prepareTestDataForStack(stack) - - stack.beginSynchronous { (transaction) in + do { - do { - - let errorExpectation = self.expectation(description: "error") - do { + try stack.perform( + synchronous: { (transaction) in - let _ = try transaction.importUniqueObject( - Into(), - source: [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, - "throw_on_insert": "" - ] - ) - XCTFail() + let errorExpectation = self.expectation(description: "error") + do { + + let _ = try transaction.importUniqueObject( + Into(), + source: [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, + "throw_on_insert": "" + ] + ) + XCTFail() + } + catch _ as TestInsertError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From()), 6) + + let object = transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 106)) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(value: 106)) + XCTAssertNil(object?.testBoolean) + XCTAssertNil(object?.testNumber) + XCTAssertNil(object?.testDecimal) + XCTAssertNil(object?.testString) + XCTAssertNil(object?.testData) + XCTAssertNil(object?.testDate) + } + self.checkExpectationsImmediately() } - catch _ as TestInsertError { + ) + try stack.perform( + synchronous: { (transaction) in - errorExpectation.fulfill() - XCTAssertEqual(transaction.fetchCount(From()), 6) - - let object = transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 106)) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testEntityID, NSNumber(value: 106)) - XCTAssertNil(object?.testBoolean) - XCTAssertNil(object?.testNumber) - XCTAssertNil(object?.testDecimal) - XCTAssertNil(object?.testString) - XCTAssertNil(object?.testData) - XCTAssertNil(object?.testDate) + let errorExpectation = self.expectation(description: "error") + do { + + let _ = try transaction.importUniqueObject( + Into(), + source: [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 105), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, + "throw_on_update": "" + ] + ) + XCTFail() + } + catch _ as TestUpdateError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From()), 6) + + let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) + + let existingObject = existingObjects?[0] + XCTAssertNotNil(existingObject) + XCTAssertEqual(existingObject?.testEntityID, NSNumber(value: 105)) + XCTAssertEqual(existingObject?.testBoolean, NSNumber(value: true)) + XCTAssertEqual(existingObject?.testNumber, NSNumber(value: 5)) + XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5")) + XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5") + XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).data(using: String.Encoding.utf8.rawValue)!) + XCTAssertEqual(existingObject?.testDate, self.dateFormatter.date(from: "2000-01-05T00:00:00Z")!) + } + self.checkExpectationsImmediately() } - catch { - - XCTFail() - } - self.checkExpectationsImmediately() - } - do { - - let errorExpectation = self.expectation(description: "error") - do { - - let _ = try transaction.importUniqueObject( - Into(), - source: [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 105), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, - "throw_on_update": "" - ] - ) - XCTFail() - } - catch _ as TestUpdateError { - - errorExpectation.fulfill() - XCTAssertEqual(transaction.fetchCount(From()), 6) - - let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) - XCTAssertNotNil(existingObjects) - XCTAssertEqual(existingObjects?.count, 1) - - let existingObject = existingObjects?[0] - XCTAssertNotNil(existingObject) - XCTAssertEqual(existingObject?.testEntityID, NSNumber(value: 105)) - XCTAssertEqual(existingObject?.testBoolean, NSNumber(value: true)) - XCTAssertEqual(existingObject?.testNumber, NSNumber(value: 5)) - XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5")) - XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5") - XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).data(using: String.Encoding.utf8.rawValue)!) - XCTAssertEqual(existingObject?.testDate, self.dateFormatter.date(from: "2000-01-05T00:00:00Z")!) - } - catch { - - XCTFail() - } - self.checkExpectationsImmediately() - } - transaction.context.reset() + ) + } + catch { + + XCTFail() } } } @@ -676,75 +688,74 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in self.prepareTestDataForStack(stack) - - stack.beginSynchronous { (transaction) in + do { - do { - - let object = try transaction.importUniqueObject( - Into(), - source: [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")! - ] - ) - XCTAssertNotNil(object) - XCTAssertEqual(transaction.fetchCount(From()), 6) - - XCTAssertEqual(object?.testEntityID, NSNumber(value: 106)) - XCTAssertEqual(object?.testBoolean, NSNumber(value: true)) - XCTAssertEqual(object?.testNumber, NSNumber(value: 6)) - XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "6")) - XCTAssertEqual(object?.testString, "nil:TestEntity1:6") - XCTAssertEqual(object?.testData, ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!) - XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!) - } - catch { - - XCTFail() - } - do { - - let object = try transaction.importUniqueObject( - Into(), - source: [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 7), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:7", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")!, - ] - ) - XCTAssertNotNil(object) - XCTAssertEqual(transaction.fetchCount(From()), 6) - - XCTAssertEqual(object?.testEntityID, NSNumber(value: 106)) - XCTAssertEqual(object?.testBoolean, NSNumber(value: false)) - XCTAssertEqual(object?.testNumber, NSNumber(value: 7)) - XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "7")) - XCTAssertEqual(object?.testString, "nil:TestEntity1:7") - XCTAssertEqual(object?.testData, ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!) - XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-07T00:00:00Z")!) - - let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 106)) - XCTAssertNotNil(existingObjects) - XCTAssertEqual(existingObjects?.count, 1) - - let existingObject = existingObjects?[0] - XCTAssertEqual(existingObject, object) - } - catch { - - XCTFail() - } - transaction.context.reset() + try stack.perform( + synchronous: { (transaction) in + + do { + + let object = try transaction.importUniqueObject( + Into(), + source: [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")! + ] + ) + XCTAssertNotNil(object) + XCTAssertEqual(transaction.fetchCount(From()), 6) + + XCTAssertEqual(object?.testEntityID, NSNumber(value: 106)) + XCTAssertEqual(object?.testBoolean, NSNumber(value: true)) + XCTAssertEqual(object?.testNumber, NSNumber(value: 6)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "6")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:6") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!) + } + do { + + let object = try transaction.importUniqueObject( + Into(), + source: [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 7), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:7", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")!, + ] + ) + XCTAssertNotNil(object) + XCTAssertEqual(transaction.fetchCount(From()), 6) + + XCTAssertEqual(object?.testEntityID, NSNumber(value: 106)) + XCTAssertEqual(object?.testBoolean, NSNumber(value: false)) + XCTAssertEqual(object?.testNumber, NSNumber(value: 7)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "7")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:7") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-07T00:00:00Z")!) + + let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 106)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) + + let existingObject = existingObjects?[0] + XCTAssertEqual(existingObject, object) + } + } + ) + } + catch { + + XCTFail() } } } @@ -755,54 +766,54 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in self.prepareTestDataForStack(stack) - - stack.beginSynchronous { (transaction) in + do { - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, - "skip_insert": "" - ], - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 107), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 7), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:7", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! + try stack.perform( + synchronous: { (transaction) in + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, + "skip_insert": "" + ], + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 107), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 7), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:7", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! + ] ] - ] - let objects = try transaction.importUniqueObjects( - Into(), - sourceArray: sourceArray - ) - XCTAssertEqual(objects.count, 1) - XCTAssertEqual(transaction.fetchCount(From()), 6) - - let object = objects[0] - let dictionary = sourceArray[1] - XCTAssertEqual(object.testEntityID, dictionary[(#keyPath(TestEntity1.testEntityID))] as? NSNumber) - XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) - XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) - XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) - XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) - XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) - XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) - } - catch { - - XCTFail() - } - transaction.context.reset() + let objects = try transaction.importUniqueObjects( + Into(), + sourceArray: sourceArray + ) + XCTAssertEqual(objects.count, 1) + XCTAssertEqual(transaction.fetchCount(From()), 6) + + let object = objects[0] + let dictionary = sourceArray[1] + XCTAssertEqual(object.testEntityID, dictionary[(#keyPath(TestEntity1.testEntityID))] as? NSNumber) + XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) + XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) + XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) + } + ) + } + catch { + + XCTFail() } } } @@ -813,158 +824,158 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in self.prepareTestDataForStack(stack) - - stack.beginSynchronous { (transaction) in + do { - let errorExpectation = self.expectation(description: "error") - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, - "throw_on_id": "" - ], - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 107), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 7), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:7", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! - ] - ] - let _ = try transaction.importUniqueObjects( - Into(), - sourceArray: sourceArray - ) - XCTFail() - } - catch _ as TestIDError { - - errorExpectation.fulfill() - XCTAssertEqual(transaction.fetchCount(From()), 5) - - XCTAssertNil(transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 106))) - XCTAssertNil(transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 107))) - } - catch { - - XCTFail() - } - self.checkExpectationsImmediately() - transaction.context.reset() + try stack.perform( + synchronous: { (transaction) in + + let errorExpectation = self.expectation(description: "error") + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, + "throw_on_id": "" + ], + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 107), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 7), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:7", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! + ] + ] + let _ = try transaction.importUniqueObjects( + Into(), + sourceArray: sourceArray + ) + XCTFail() + } + catch _ as TestIDError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From()), 5) + + XCTAssertNil(transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 106))) + XCTAssertNil(transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 107))) + } + transaction.unsafeContext().reset() + self.checkExpectationsImmediately() + } + ) + try stack.perform( + synchronous: { (transaction) in + + let errorExpectation = self.expectation(description: "error") + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: true), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, + "throw_on_insert": "" + ], + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 107), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 7), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:7", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! + ] + ] + let _ = try transaction.importUniqueObjects( + Into(), + sourceArray: sourceArray + ) + XCTFail() + } + catch _ as TestInsertError { + + errorExpectation.fulfill() + + let object = transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 106)) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(value: 106)) + XCTAssertNil(object?.testBoolean) + XCTAssertNil(object?.testNumber) + XCTAssertNil(object?.testDecimal) + XCTAssertNil(object?.testString) + XCTAssertNil(object?.testData) + XCTAssertNil(object?.testDate) + } + transaction.context.reset() + self.checkExpectationsImmediately() + } + ) + try stack.perform( + synchronous: { (transaction) in + + let errorExpectation = self.expectation(description: "error") + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 105), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, + "throw_on_update": "" + ] + ] + let _ = try transaction.importUniqueObjects( + Into(), + sourceArray: sourceArray + ) + XCTFail() + } + catch _ as TestUpdateError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From()), 5) + + let object = transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(value: 105)) + XCTAssertEqual(object?.testBoolean, NSNumber(value: true)) + XCTAssertEqual(object?.testNumber, NSNumber(value: 5)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "5")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:5") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:5" as NSString).data(using: String.Encoding.utf8.rawValue)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-05T00:00:00Z")!) + + let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) + + let existingObject = existingObjects?[0] + XCTAssertEqual(existingObject, object) + } + transaction.context.reset() + self.checkExpectationsImmediately() + } + ) } - stack.beginSynchronous { (transaction) in + catch { - let errorExpectation = self.expectation(description: "error") - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: true), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, - "throw_on_insert": "" - ], - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 107), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 7), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "7"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:7", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:7" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-07T00:00:00Z")! - ] - ] - let _ = try transaction.importUniqueObjects( - Into(), - sourceArray: sourceArray - ) - XCTFail() - } - catch _ as TestInsertError { - - errorExpectation.fulfill() - - let object = transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 106)) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testEntityID, NSNumber(value: 106)) - XCTAssertNil(object?.testBoolean) - XCTAssertNil(object?.testNumber) - XCTAssertNil(object?.testDecimal) - XCTAssertNil(object?.testString) - XCTAssertNil(object?.testData) - XCTAssertNil(object?.testDate) - } - catch { - - XCTFail() - } - self.checkExpectationsImmediately() - transaction.context.reset() - } - stack.beginSynchronous { (transaction) in - - let errorExpectation = self.expectation(description: "error") - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 105), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")!, - "throw_on_update": "" - ] - ] - let _ = try transaction.importUniqueObjects( - Into(), - sourceArray: sourceArray - ) - XCTFail() - } - catch _ as TestUpdateError { - - errorExpectation.fulfill() - XCTAssertEqual(transaction.fetchCount(From()), 5) - - let object = transaction.fetchOne(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testEntityID, NSNumber(value: 105)) - XCTAssertEqual(object?.testBoolean, NSNumber(value: true)) - XCTAssertEqual(object?.testNumber, NSNumber(value: 5)) - XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "5")) - XCTAssertEqual(object?.testString, "nil:TestEntity1:5") - XCTAssertEqual(object?.testData, ("nil:TestEntity1:5" as NSString).data(using: String.Encoding.utf8.rawValue)!) - XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-05T00:00:00Z")!) - - let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) - XCTAssertNotNil(existingObjects) - XCTAssertEqual(existingObjects?.count, 1) - - let existingObject = existingObjects?[0] - XCTAssertEqual(existingObject, object) - } - catch { - - XCTFail() - } - self.checkExpectationsImmediately() - transaction.context.reset() + XCTFail() } } } @@ -975,62 +986,62 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in self.prepareTestDataForStack(stack) - - stack.beginSynchronous { (transaction) in + do { - do { - - let sourceArray: [TestEntity1.ImportSource] = [ - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 105), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 15), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "15"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:15", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:15" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-15T00:00:00Z")! - ], - [ - #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), - #keyPath(TestEntity1.testBoolean): NSNumber(value: false), - #keyPath(TestEntity1.testNumber): NSNumber(value: 6), - #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), - #keyPath(TestEntity1.testString): "nil:TestEntity1:6", - #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, - #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")! + try stack.perform( + synchronous: { (transaction) in + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 105), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 15), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "15"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:15", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:15" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-15T00:00:00Z")! + ], + [ + #keyPath(TestEntity1.testEntityID): NSNumber(value: 106), + #keyPath(TestEntity1.testBoolean): NSNumber(value: false), + #keyPath(TestEntity1.testNumber): NSNumber(value: 6), + #keyPath(TestEntity1.testDecimal): NSDecimalNumber(string: "6"), + #keyPath(TestEntity1.testString): "nil:TestEntity1:6", + #keyPath(TestEntity1.testData): ("nil:TestEntity1:6" as NSString).data(using: String.Encoding.utf8.rawValue)!, + #keyPath(TestEntity1.testDate): self.dateFormatter.date(from: "2000-01-06T00:00:00Z")! + ] ] - ] - let objects = try transaction.importUniqueObjects( - Into(), - sourceArray: sourceArray - ) - XCTAssertEqual(objects.count, sourceArray.count) - XCTAssertEqual(transaction.fetchCount(From()), 6) - for i in 0 ..< sourceArray.count { + let objects = try transaction.importUniqueObjects( + Into(), + sourceArray: sourceArray + ) + XCTAssertEqual(objects.count, sourceArray.count) + XCTAssertEqual(transaction.fetchCount(From()), 6) + for i in 0 ..< sourceArray.count { + + let object = objects[i] + let dictionary = sourceArray[i] + + XCTAssertEqual(object.testEntityID, dictionary[(#keyPath(TestEntity1.testEntityID))] as? NSNumber) + XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) + XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) + XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) + } + let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) - let object = objects[i] - let dictionary = sourceArray[i] - - XCTAssertEqual(object.testEntityID, dictionary[(#keyPath(TestEntity1.testEntityID))] as? NSNumber) - XCTAssertEqual(object.testBoolean, dictionary[(#keyPath(TestEntity1.testBoolean))] as? NSNumber) - XCTAssertEqual(object.testNumber, dictionary[(#keyPath(TestEntity1.testNumber))] as? NSNumber) - XCTAssertEqual(object.testDecimal, dictionary[(#keyPath(TestEntity1.testDecimal))] as? NSDecimalNumber) - XCTAssertEqual(object.testString, dictionary[(#keyPath(TestEntity1.testString))] as? String) - XCTAssertEqual(object.testData, dictionary[(#keyPath(TestEntity1.testData))] as? Data) - XCTAssertEqual(object.testDate, dictionary[(#keyPath(TestEntity1.testDate))] as? Date) + let existingObject = existingObjects?[0] + XCTAssertEqual(existingObject, objects[0]) } - let existingObjects = transaction.fetchAll(From(), Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 105)) - XCTAssertNotNil(existingObjects) - XCTAssertEqual(existingObjects?.count, 1) - - let existingObject = existingObjects?[0] - XCTAssertEqual(existingObject, objects[0]) - } - catch { - - XCTFail() - } - transaction.context.reset() + ) + } + catch { + + XCTFail() } } } diff --git a/CoreStoreTests/ListObserverTests.swift b/CoreStoreTests/ListObserverTests.swift index f84cd4c..b9ff213 100644 --- a/CoreStoreTests/ListObserverTests.swift +++ b/CoreStoreTests/ListObserverTests.swift @@ -149,29 +149,29 @@ class ListObserverTests: BaseTestDataTestCase { } ) let saveExpectation = self.expectation(description: "save") - stack.beginAsynchronous { (transaction) in - - let object = transaction.create(Into()) - object.testBoolean = NSNumber(value: true) - object.testNumber = NSNumber(value: 1) - object.testDecimal = NSDecimalNumber(string: "1") - object.testString = "nil:TestEntity1:1" - object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)! - object.testDate = self.dateFormatter.date(from: "2000-01-01T00:00:00Z")! - - transaction.commit { (result) in + stack.perform( + asynchronous: { (transaction) -> Bool in - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - saveExpectation.fulfill() - - case .failure: - XCTFail() - } + let object = transaction.create(Into()) + object.testBoolean = NSNumber(value: true) + object.testNumber = NSNumber(value: 1) + object.testDecimal = NSDecimalNumber(string: "1") + object.testString = "nil:TestEntity1:1" + object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)! + object.testDate = self.dateFormatter.date(from: "2000-01-01T00:00:00Z")! + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + }, + failure: { _ in + + XCTFail() } - } + ) self.waitAndCheckExpectations() } } @@ -283,49 +283,49 @@ class ListObserverTests: BaseTestDataTestCase { } ) let saveExpectation = self.expectation(description: "save") - stack.beginAsynchronous { (transaction) in - - if let object = transaction.fetchOne( - From(), - Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) { + stack.perform( + asynchronous: { (transaction) -> Bool in - object.testNumber = NSNumber(value: 11) - object.testDecimal = NSDecimalNumber(string: "11") - object.testString = "nil:TestEntity1:11" - object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)! - object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")! - } - else { - - XCTFail() - } - if let object = transaction.fetchOne( - From(), - Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) { - - object.testNumber = NSNumber(value: 22) - object.testDecimal = NSDecimalNumber(string: "22") - object.testString = "nil:TestEntity1:22" - object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)! - object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")! - } - else { - - XCTFail() - } - transaction.commit { (result) in - - switch result { + if let object = transaction.fetchOne( + From(), + Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) { - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - saveExpectation.fulfill() + object.testNumber = NSNumber(value: 11) + object.testDecimal = NSDecimalNumber(string: "11") + object.testString = "nil:TestEntity1:11" + object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)! + object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")! + } + else { - case .failure: XCTFail() } + if let object = transaction.fetchOne( + From(), + Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) { + + object.testNumber = NSNumber(value: 22) + object.testDecimal = NSDecimalNumber(string: "22") + object.testString = "nil:TestEntity1:22" + object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)! + object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")! + } + else { + + XCTFail() + } + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + }, + failure: { _ in + + XCTFail() } - } + ) self.waitAndCheckExpectations() } } @@ -409,31 +409,31 @@ class ListObserverTests: BaseTestDataTestCase { } ) let saveExpectation = self.expectation(description: "save") - stack.beginAsynchronous { (transaction) in - - if let object = transaction.fetchOne( - From(), - Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) { + stack.perform( + asynchronous: { (transaction) -> Bool in - object.testBoolean = NSNumber(value: true) - } - else { + if let object = transaction.fetchOne( + From(), + Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) { + + object.testBoolean = NSNumber(value: true) + } + else { + + XCTFail() + } + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + }, + failure: { _ in XCTFail() } - transaction.commit { (result) in - - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - saveExpectation.fulfill() - - case .failure: - XCTFail() - } - } - } + ) self.waitAndCheckExpectations() } } @@ -544,25 +544,25 @@ class ListObserverTests: BaseTestDataTestCase { } ) let saveExpectation = self.expectation(description: "save") - stack.beginAsynchronous { (transaction) in - - transaction.deleteAll( - From(), - Where(#keyPath(TestEntity1.testBoolean), isEqualTo: false) - ) - transaction.commit { (result) in + stack.perform( + asynchronous: { (transaction) -> Bool in - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - saveExpectation.fulfill() - - case .failure: - XCTFail() - } + transaction.deleteAll( + From(), + Where(#keyPath(TestEntity1.testBoolean), isEqualTo: false) + ) + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + }, + failure: { _ in + + XCTFail() } - } + ) self.waitAndCheckExpectations() } } diff --git a/CoreStoreTests/ObjectObserverTests.swift b/CoreStoreTests/ObjectObserverTests.swift index e2fa9f8..df3f91e 100644 --- a/CoreStoreTests/ObjectObserverTests.swift +++ b/CoreStoreTests/ObjectObserverTests.swift @@ -125,29 +125,29 @@ import CoreStore } ) let saveExpectation = self.expectation(description: "save") - stack.beginAsynchronous { (transaction) in - - guard let object = transaction.edit(object) else { + stack.perform( + asynchronous: { (transaction) -> Bool in + + guard let object = transaction.edit(object) else { + + XCTFail() + try transaction.cancel() + } + object.testNumber = NSNumber(value: 10) + object.testString = "nil:TestEntity1:10" + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + }, + failure: { _ in XCTFail() - return } - object.testNumber = NSNumber(value: 10) - object.testString = "nil:TestEntity1:10" - - transaction.commit { (result) in - - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - saveExpectation.fulfill() - - case .failure: - XCTFail() - } - } - } + ) self.waitAndCheckExpectations() } } @@ -193,29 +193,29 @@ import CoreStore } ) let saveExpectation = self.expectation(description: "save") - stack.beginAsynchronous { (transaction) in - - guard let object = transaction.edit(object) else { + stack.perform( + asynchronous: { (transaction) -> Bool in + + guard let object = transaction.edit(object) else { + + XCTFail() + try transaction.cancel() + } + transaction.delete(object) + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + XCTAssertTrue(monitor.isObjectDeleted) + saveExpectation.fulfill() + }, + failure: { _ in XCTFail() - return } - transaction.delete(object) - - transaction.commit { (result) in - - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - XCTAssertTrue(monitor.isObjectDeleted) - saveExpectation.fulfill() - - case .failure: - XCTFail() - } - } - } + ) self.waitAndCheckExpectations() } } diff --git a/CoreStoreTests/TransactionTests.swift b/CoreStoreTests/TransactionTests.swift index f842f66..f88f5a9 100644 --- a/CoreStoreTests/TransactionTests.swift +++ b/CoreStoreTests/TransactionTests.swift @@ -49,13 +49,13 @@ final class TransactionTests: BaseTestCase { createExpectation.fulfill() } - XCTAssertEqual(transaction.context, transaction.internalContext()) + XCTAssertEqual(transaction.context, transaction.unsafeContext()) XCTAssertTrue(transaction.context.isTransactionContext) XCTAssertFalse(transaction.context.isDataStackContext) let object = transaction.create(Into()) - XCTAssertEqual(object.fetchSource()?.internalContext(), transaction.context) - XCTAssertEqual(object.querySource()?.internalContext(), transaction.context) + XCTAssertEqual(object.fetchSource()?.unsafeContext(), transaction.context) + XCTAssertEqual(object.querySource()?.unsafeContext(), transaction.context) object.testEntityID = NSNumber(value: 1) object.testString = "string1" @@ -64,7 +64,7 @@ final class TransactionTests: BaseTestCase { return transaction.hasChanges }, - waitForObserverNotifications: true + waitForAllObservers: true ) self.checkExpectationsImmediately() XCTAssertTrue(hasChanges) @@ -73,8 +73,8 @@ final class TransactionTests: BaseTestCase { let object = stack.fetchOne(From()) XCTAssertNotNil(object) - XCTAssertEqual(object?.fetchSource()?.internalContext(), stack.mainContext) - XCTAssertEqual(object?.querySource()?.internalContext(), stack.mainContext) + XCTAssertEqual(object?.fetchSource()?.unsafeContext(), stack.mainContext) + XCTAssertEqual(object?.querySource()?.unsafeContext(), stack.mainContext) XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) XCTAssertEqual(object?.testString, "string1") @@ -102,7 +102,7 @@ final class TransactionTests: BaseTestCase { return transaction.hasChanges }, - waitForObserverNotifications: true + waitForAllObservers: true ) self.checkExpectationsImmediately() XCTAssertTrue(hasChanges) @@ -119,20 +119,25 @@ final class TransactionTests: BaseTestCase { do { let deleteExpectation = self.expectation(description: "delete") - stack.beginSynchronous { (transaction) in + do { - let object = transaction.fetchOne(From()) - transaction.delete(object) + let hasChanges: Bool = try stack.perform( + synchronous: { (transaction) in + + defer { + + deleteExpectation.fulfill() + } + let object = transaction.fetchOne(From()) + transaction.delete(object) + return transaction.hasChanges + } + ) + XCTAssertTrue(hasChanges) + } + catch { - switch transaction.commitAndWait() { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - deleteExpectation.fulfill() - - default: - XCTFail() - } + XCTFail() } self.checkExpectationsImmediately() @@ -153,23 +158,29 @@ final class TransactionTests: BaseTestCase { do { let createExpectation = self.expectation(description: "create") - stack.beginSynchronous { (transaction) in + do { - let object = transaction.create(Into("Config1")) - object.testEntityID = NSNumber(value: 1) - object.testString = "string1" - object.testNumber = 100 - object.testDate = testDate - - switch transaction.commitAndWait() { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - createExpectation.fulfill() - - default: - XCTFail() - } + let hasChanges: Bool = try stack.perform( + synchronous: { (transaction) in + + defer { + + createExpectation.fulfill() + } + let object = transaction.create(Into("Config1")) + object.testEntityID = NSNumber(value: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = testDate + + return transaction.hasChanges + } + ) + XCTAssertTrue(hasChanges) + } + catch { + + XCTFail() } self.checkExpectationsImmediately() @@ -186,26 +197,32 @@ final class TransactionTests: BaseTestCase { do { let updateExpectation = self.expectation(description: "update") - stack.beginSynchronous { (transaction) in + do { - guard let object = transaction.fetchOne(From("Config1")) else { - - XCTFail() - return - } - object.testString = "string1_edit" - object.testNumber = 200 - object.testDate = Date.distantFuture + let hasChanges: Bool = try stack.perform( + synchronous: { (transaction) in + + defer { + + updateExpectation.fulfill() + } + guard let object = transaction.fetchOne(From("Config1")) else { + + XCTFail() + try transaction.cancel() + } + object.testString = "string1_edit" + object.testNumber = 200 + object.testDate = Date.distantFuture + + return transaction.hasChanges + } + ) + XCTAssertTrue(hasChanges) + } + catch { - switch transaction.commitAndWait() { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - updateExpectation.fulfill() - - default: - XCTFail() - } + XCTFail() } self.checkExpectationsImmediately() @@ -222,20 +239,26 @@ final class TransactionTests: BaseTestCase { do { let deleteExpectation = self.expectation(description: "delete") - stack.beginSynchronous { (transaction) in + do { - let object = transaction.fetchOne(From("Config1")) - transaction.delete(object) + let hasChanges: Bool = try stack.perform( + synchronous: { (transaction) in + + defer { + + deleteExpectation.fulfill() + } + let object = transaction.fetchOne(From("Config1")) + transaction.delete(object) + + return transaction.hasChanges + } + ) + XCTAssertTrue(hasChanges) + } + catch { - switch transaction.commitAndWait() { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - deleteExpectation.fulfill() - - default: - XCTFail() - } + XCTFail() } self.checkExpectationsImmediately() @@ -253,18 +276,22 @@ final class TransactionTests: BaseTestCase { do { let createDiscardExpectation = self.expectation(description: "create-discard") - let loggerExpectations = self.prepareLoggerExpectations([.logWarning]) - stack.beginSynchronous { (transaction) in - - let object = transaction.create(Into()) - object.testEntityID = NSNumber(value: 1) - object.testString = "string1" - object.testNumber = 100 - object.testDate = Date() - - createDiscardExpectation.fulfill() - self.expectLogger(loggerExpectations) - } + _ = try? stack.perform( + synchronous: { (transaction) in + + defer { + + createDiscardExpectation.fulfill() + } + let object = transaction.create(Into()) + object.testEntityID = NSNumber(value: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = Date() + + try transaction.cancel() + } + ) self.checkExpectationsImmediately() XCTAssertEqual(stack.fetchCount(From()), 0) @@ -276,43 +303,44 @@ final class TransactionTests: BaseTestCase { do { let createExpectation = self.expectation(description: "create") - stack.beginSynchronous { (transaction) in - - let object = transaction.create(Into()) - object.testEntityID = NSNumber(value: 1) - object.testString = "string1" - object.testNumber = 100 - object.testDate = testDate - - switch transaction.commitAndWait() { + let dataPrepared: Void? = try? stack.perform( + synchronous: { (transaction) in - case .success(true): - createExpectation.fulfill() - - default: - XCTFail() + let object = transaction.create(Into()) + object.testEntityID = NSNumber(value: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = testDate } + ) + if dataPrepared != nil { + + createExpectation.fulfill() } self.checkExpectationsImmediately() } do { let updateDiscardExpectation = self.expectation(description: "update-discard") - let loggerExpectations = self.prepareLoggerExpectations([.logWarning]) - stack.beginSynchronous { (transaction) in - - guard let object = transaction.fetchOne(From()) else { + _ = try? stack.perform( + synchronous: { (transaction) in - XCTFail() - return + defer { + + updateDiscardExpectation.fulfill() + } + guard let object = transaction.fetchOne(From()) else { + + XCTFail() + try transaction.cancel() + } + object.testString = "string1_edit" + object.testNumber = 200 + object.testDate = Date.distantFuture + + try transaction.cancel() } - object.testString = "string1_edit" - object.testNumber = 200 - object.testDate = Date.distantFuture - - updateDiscardExpectation.fulfill() - self.expectLogger(loggerExpectations) - } + ) self.checkExpectationsImmediately() XCTAssertEqual(stack.fetchCount(From()), 1) @@ -327,19 +355,23 @@ final class TransactionTests: BaseTestCase { do { let deleteDiscardExpectation = self.expectation(description: "delete-discard") - let loggerExpectations = self.prepareLoggerExpectations([.logWarning]) - stack.beginSynchronous { (transaction) in - - guard let object = transaction.fetchOne(From()) else { + _ = try? stack.perform( + synchronous: { (transaction) in - XCTFail() - return + defer { + + deleteDiscardExpectation.fulfill() + } + guard let object = transaction.fetchOne(From()) else { + + XCTFail() + try transaction.cancel() + } + transaction.delete(object) + + try transaction.cancel() } - transaction.delete(object) - - deleteDiscardExpectation.fulfill() - self.expectLogger(loggerExpectations) - } + ) self.checkExpectationsImmediately() XCTAssertEqual(stack.fetchCount(From()), 1) @@ -429,23 +461,27 @@ final class TransactionTests: BaseTestCase { } ) let saveExpectation = self.expectation(description: "save") - stack.beginSynchronous { (transaction) in + do { - let object = transaction.create(Into()) - object.testBoolean = NSNumber(value: true) - object.testNumber = NSNumber(value: 1) - object.testDecimal = NSDecimalNumber(string: "1") - object.testString = "nil:TestEntity1:1" + let hasChanges: Bool = try stack.perform( + synchronous: { (transaction) in + + let object = transaction.create(Into()) + object.testBoolean = NSNumber(value: true) + object.testNumber = NSNumber(value: 1) + object.testDecimal = NSDecimalNumber(string: "1") + object.testString = "nil:TestEntity1:1" + + return transaction.hasChanges + }, + waitForAllObservers: false + ) + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + } + catch { - switch transaction.commit() { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - saveExpectation.fulfill() - - case .failure: - XCTFail() - } + XCTFail() } XCTAssertEqual(events, 0) XCTAssertEqual(monitor.numberOfObjects(), 0) @@ -464,110 +500,110 @@ final class TransactionTests: BaseTestCase { do { let createExpectation = self.expectation(description: "create") - stack.beginAsynchronous { (transaction) in - - XCTAssertEqual(transaction.context, transaction.internalContext()) - XCTAssertTrue(transaction.context.isTransactionContext) - XCTAssertFalse(transaction.context.isDataStackContext) - - let object = transaction.create(Into()) - XCTAssertEqual(object.fetchSource()?.internalContext(), transaction.context) - XCTAssertEqual(object.querySource()?.internalContext(), transaction.context) - - object.testEntityID = NSNumber(value: 1) - object.testString = "string1" - object.testNumber = 100 - object.testDate = testDate - - transaction.commit { (result) in + stack.perform( + asynchronous: { (transaction) -> Bool in - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - - XCTAssertEqual(stack.fetchCount(From()), 1) - - let object = stack.fetchOne(From()) - XCTAssertNotNil(object) - XCTAssertEqual(object?.fetchSource()?.internalContext(), stack.mainContext) - XCTAssertEqual(object?.querySource()?.internalContext(), stack.mainContext) - - XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) - XCTAssertEqual(object?.testString, "string1") - XCTAssertEqual(object?.testNumber, 100) - XCTAssertEqual(object?.testDate, testDate) - createExpectation.fulfill() - - default: - XCTFail() - } + XCTAssertEqual(transaction.context, transaction.unsafeContext()) + XCTAssertTrue(transaction.context.isTransactionContext) + XCTAssertFalse(transaction.context.isDataStackContext) + + let object = transaction.create(Into()) + XCTAssertEqual(object.fetchSource()?.unsafeContext(), transaction.context) + XCTAssertEqual(object.querySource()?.unsafeContext(), transaction.context) + + object.testEntityID = NSNumber(value: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = testDate + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From()), 1) + + let object = stack.fetchOne(From()) + XCTAssertNotNil(object) + XCTAssertEqual(object?.fetchSource()?.unsafeContext(), stack.mainContext) + XCTAssertEqual(object?.querySource()?.unsafeContext(), stack.mainContext) + + XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) + XCTAssertEqual(object?.testString, "string1") + XCTAssertEqual(object?.testNumber, 100) + XCTAssertEqual(object?.testDate, testDate) + createExpectation.fulfill() + }, + failure: { _ in + + XCTFail() } - } + ) } do { let updateExpectation = self.expectation(description: "update") - stack.beginAsynchronous { (transaction) in - - guard let object = transaction.fetchOne(From()) else { + stack.perform( + asynchronous: { (transaction) -> Bool in + + guard let object = transaction.fetchOne(From()) else { + + XCTFail() + try transaction.cancel() + } + object.testString = "string1_edit" + object.testNumber = 200 + object.testDate = Date.distantFuture + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From()), 1) + + let object = stack.fetchOne(From()) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) + XCTAssertEqual(object?.testString, "string1_edit") + XCTAssertEqual(object?.testNumber, 200) + XCTAssertEqual(object?.testDate, Date.distantFuture) + updateExpectation.fulfill() + }, + failure: { _ in XCTFail() - return } - object.testString = "string1_edit" - object.testNumber = 200 - object.testDate = Date.distantFuture - - transaction.commit { (result) in - - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - - XCTAssertEqual(stack.fetchCount(From()), 1) - - let object = stack.fetchOne(From()) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) - XCTAssertEqual(object?.testString, "string1_edit") - XCTAssertEqual(object?.testNumber, 200) - XCTAssertEqual(object?.testDate, Date.distantFuture) - updateExpectation.fulfill() - - default: - XCTFail() - } - } - } + ) } do { let deleteExpectation = self.expectation(description: "delete") - stack.beginAsynchronous { (transaction) in - - let object = transaction.fetchOne(From()) - transaction.delete(object) - - transaction.commit { (result) in + stack.perform( + asynchronous: { (transaction) -> Bool in - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - - XCTAssertEqual(stack.fetchCount(From()), 0) - - let object = stack.fetchOne(From()) - XCTAssertNil(object) - deleteExpectation.fulfill() - - default: - XCTFail() - } + let object = transaction.fetchOne(From()) + transaction.delete(object) + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From()), 0) + + let object = stack.fetchOne(From()) + XCTAssertNil(object) + deleteExpectation.fulfill() + }, + failure: { _ in + + XCTFail() } - } + ) } } self.waitAndCheckExpectations() @@ -582,101 +618,101 @@ final class TransactionTests: BaseTestCase { do { let createExpectation = self.expectation(description: "create") - stack.beginAsynchronous { (transaction) in - - let object = transaction.create(Into("Config1")) - object.testEntityID = NSNumber(value: 1) - object.testString = "string1" - object.testNumber = 100 - object.testDate = testDate - - transaction.commit { (result) in + stack.perform( + asynchronous: { (transaction) -> Bool in - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - - XCTAssertEqual(stack.fetchCount(From("Config1")), 1) - XCTAssertEqual(stack.fetchCount(From(nil)), 0) - - let object = stack.fetchOne(From("Config1")) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) - XCTAssertEqual(object?.testString, "string1") - XCTAssertEqual(object?.testNumber, 100) - XCTAssertEqual(object?.testDate, testDate) - createExpectation.fulfill() - - default: - XCTFail() - } + let object = transaction.create(Into("Config1")) + object.testEntityID = NSNumber(value: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = testDate + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From("Config1")), 1) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + let object = stack.fetchOne(From("Config1")) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) + XCTAssertEqual(object?.testString, "string1") + XCTAssertEqual(object?.testNumber, 100) + XCTAssertEqual(object?.testDate, testDate) + createExpectation.fulfill() + }, + failure: { _ in + + XCTFail() } - } + ) } do { let updateExpectation = self.expectation(description: "update") - stack.beginAsynchronous { (transaction) in - - guard let object = transaction.fetchOne(From("Config1")) else { + stack.perform( + asynchronous: { (transaction) -> Bool in + + guard let object = transaction.fetchOne(From("Config1")) else { + + XCTFail() + try transaction.cancel() + } + object.testString = "string1_edit" + object.testNumber = 200 + object.testDate = Date.distantFuture + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From("Config1")), 1) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + let object = stack.fetchOne(From("Config1")) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) + XCTAssertEqual(object?.testString, "string1_edit") + XCTAssertEqual(object?.testNumber, 200) + XCTAssertEqual(object?.testDate, Date.distantFuture) + updateExpectation.fulfill() + }, + failure: { _ in XCTFail() - return } - object.testString = "string1_edit" - object.testNumber = 200 - object.testDate = Date.distantFuture - - transaction.commit { (result) in - - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - - XCTAssertEqual(stack.fetchCount(From("Config1")), 1) - XCTAssertEqual(stack.fetchCount(From(nil)), 0) - - let object = stack.fetchOne(From("Config1")) - XCTAssertNotNil(object) - XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) - XCTAssertEqual(object?.testString, "string1_edit") - XCTAssertEqual(object?.testNumber, 200) - XCTAssertEqual(object?.testDate, Date.distantFuture) - updateExpectation.fulfill() - - default: - XCTFail() - } - } - } + ) } do { let deleteExpectation = self.expectation(description: "delete") - stack.beginAsynchronous { (transaction) in - - let object = transaction.fetchOne(From("Config1")) - transaction.delete(object) - - transaction.commit { (result) in + stack.perform( + asynchronous: { (transaction) -> Bool in - switch result { - - case .success(let hasChanges): - XCTAssertTrue(hasChanges) - - XCTAssertEqual(stack.fetchCount(From("Config1")), 0) - XCTAssertEqual(stack.fetchCount(From(nil)), 0) - - deleteExpectation.fulfill() - - default: - XCTFail() - } + let object = transaction.fetchOne(From("Config1")) + transaction.delete(object) + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From("Config1")), 0) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + deleteExpectation.fulfill() + }, + failure: { _ in + + XCTFail() } - } + ) } } self.waitAndCheckExpectations() @@ -690,89 +726,116 @@ final class TransactionTests: BaseTestCase { do { let createDiscardExpectation = self.expectation(description: "create-discard") - let loggerExpectations = self.prepareLoggerExpectations([.logWarning]) - stack.beginAsynchronous { (transaction) in - - let object = transaction.create(Into()) - object.testEntityID = NSNumber(value: 1) - object.testString = "string1" - object.testNumber = 100 - object.testDate = Date() - - createDiscardExpectation.fulfill() - self.expectLogger(loggerExpectations) - } + stack.perform( + asynchronous: { (transaction) -> Void in + + let object = transaction.create(Into()) + object.testEntityID = NSNumber(value: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = Date() + + createDiscardExpectation.fulfill() + try transaction.cancel() + }, + success: { + + XCTFail() + }, + failure: { (error) in + + XCTAssertEqual(error, CoreStoreError.userCancelled) + } + ) } let testDate = Date() do { let createExpectation = self.expectation(description: "create") - stack.beginAsynchronous { (transaction) in - - XCTAssertEqual(transaction.fetchCount(From()), 0) - XCTAssertNil(transaction.fetchOne(From())) - - let object = transaction.create(Into()) - object.testEntityID = NSNumber(value: 1) - object.testString = "string1" - object.testNumber = 100 - object.testDate = testDate - - transaction.commit { (result) in + stack.perform( + asynchronous: { (transaction) -> Bool in - switch result { - - case .success(true): - createExpectation.fulfill() - - default: - XCTFail() - } + XCTAssertEqual(transaction.fetchCount(From()), 0) + XCTAssertNil(transaction.fetchOne(From())) + + let object = transaction.create(Into()) + object.testEntityID = NSNumber(value: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = testDate + + return transaction.hasChanges + }, + success: { (hasChanges) in + + XCTAssertTrue(hasChanges) + createExpectation.fulfill() + }, + failure: { _ in + + XCTFail() } - } + ) } do { let updateDiscardExpectation = self.expectation(description: "update-discard") - let loggerExpectations = self.prepareLoggerExpectations([.logWarning]) - stack.beginAsynchronous { (transaction) in - - guard let object = transaction.fetchOne(From()) else { + stack.perform( + asynchronous: { (transaction) -> Void in + + guard let object = transaction.fetchOne(From()) else { + + XCTFail() + return + } + object.testString = "string1_edit" + object.testNumber = 200 + object.testDate = Date.distantFuture + + updateDiscardExpectation.fulfill() + + try transaction.cancel() + }, + success: { XCTFail() - return + }, + failure: { (error) in + + XCTAssertEqual(error, CoreStoreError.userCancelled) } - object.testString = "string1_edit" - object.testNumber = 200 - object.testDate = Date.distantFuture - - updateDiscardExpectation.fulfill() - self.expectLogger(loggerExpectations) - } + ) } do { let deleteDiscardExpectation = self.expectation(description: "delete-discard") - let loggerExpectations = self.prepareLoggerExpectations([.logWarning]) - stack.beginAsynchronous { (transaction) in - - XCTAssertEqual(transaction.fetchCount(From()), 1) - - guard let object = transaction.fetchOne(From()) else { + stack.perform( + asynchronous: { (transaction) -> Void in + + XCTAssertEqual(transaction.fetchCount(From()), 1) + + guard let object = transaction.fetchOne(From()) else { + + XCTFail() + try transaction.cancel() + } + XCTAssertNotNil(object) + XCTAssertEqual(object.testEntityID, NSNumber(value: 1)) + XCTAssertEqual(object.testString, "string1") + XCTAssertEqual(object.testNumber, 100) + XCTAssertEqual(object.testDate, testDate) + + transaction.delete(object) + + try transaction.cancel() + }, + success: { XCTFail() - return - } - XCTAssertNotNil(object) - XCTAssertEqual(object.testEntityID, NSNumber(value: 1)) - XCTAssertEqual(object.testString, "string1") - XCTAssertEqual(object.testNumber, 100) - XCTAssertEqual(object.testDate, testDate) - - transaction.delete(object) - - DispatchQueue.main.async { + }, + failure: { (error) in + XCTAssertEqual(error, CoreStoreError.userCancelled) XCTAssertEqual(stack.fetchCount(From()), 1) let object = stack.fetchOne(From()) @@ -783,8 +846,7 @@ final class TransactionTests: BaseTestCase { XCTAssertEqual(object?.testDate, testDate) deleteDiscardExpectation.fulfill() } - self.expectLogger(loggerExpectations) - } + ) } } self.waitAndCheckExpectations() @@ -796,7 +858,7 @@ final class TransactionTests: BaseTestCase { self.prepareStack { (stack) in let transaction = stack.beginUnsafe() - XCTAssertEqual(transaction.context, transaction.internalContext()) + XCTAssertEqual(transaction.context, transaction.unsafeContext()) XCTAssertTrue(transaction.context.isTransactionContext) XCTAssertFalse(transaction.context.isDataStackContext) @@ -804,31 +866,33 @@ final class TransactionTests: BaseTestCase { do { let object = transaction.create(Into()) - XCTAssertEqual(object.fetchSource()?.internalContext(), transaction.context) - XCTAssertEqual(object.querySource()?.internalContext(), transaction.context) + XCTAssertEqual(object.fetchSource()?.unsafeContext(), transaction.context) + XCTAssertEqual(object.querySource()?.unsafeContext(), transaction.context) object.testEntityID = NSNumber(value: 1) object.testString = "string1" object.testNumber = 100 object.testDate = testDate - switch transaction.commitAndWait() { + do { + + XCTAssertTrue(transaction.hasChanges) + try transaction.commitAndWait() - case .success(let hasChanges): - XCTAssertTrue(hasChanges) XCTAssertEqual(stack.fetchCount(From()), 1) let object = stack.fetchOne(From()) XCTAssertNotNil(object) - XCTAssertEqual(object?.fetchSource()?.internalContext(), stack.mainContext) - XCTAssertEqual(object?.querySource()?.internalContext(), stack.mainContext) + XCTAssertEqual(object?.fetchSource()?.unsafeContext(), stack.mainContext) + XCTAssertEqual(object?.querySource()?.unsafeContext(), stack.mainContext) XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) XCTAssertEqual(object?.testString, "string1") XCTAssertEqual(object?.testNumber, 100) XCTAssertEqual(object?.testDate, testDate) + } + catch { - default: XCTFail() } } @@ -843,10 +907,11 @@ final class TransactionTests: BaseTestCase { object.testNumber = 200 object.testDate = Date.distantFuture - switch transaction.commitAndWait() { + do { + + XCTAssertTrue(transaction.hasChanges) + try transaction.commitAndWait() - case .success(let hasChanges): - XCTAssertTrue(hasChanges) XCTAssertEqual(stack.fetchCount(From()), 1) let object = stack.fetchOne(From()) @@ -855,8 +920,9 @@ final class TransactionTests: BaseTestCase { XCTAssertEqual(object?.testString, "string1_edit") XCTAssertEqual(object?.testNumber, 200) XCTAssertEqual(object?.testDate, Date.distantFuture) + } + catch { - default: XCTFail() } } @@ -865,15 +931,16 @@ final class TransactionTests: BaseTestCase { let object = transaction.fetchOne(From()) transaction.delete(object) - switch transaction.commitAndWait() { + do { - case .success(let hasChanges): - XCTAssertTrue(hasChanges) + XCTAssertTrue(transaction.hasChanges) + try transaction.commitAndWait() XCTAssertEqual(stack.fetchCount(From()), 0) XCTAssertNil(stack.fetchOne(From())) + } + catch { - default: XCTFail() } } @@ -896,10 +963,11 @@ final class TransactionTests: BaseTestCase { object.testNumber = 100 object.testDate = testDate - switch transaction.commitAndWait() { + do { + + XCTAssertTrue(transaction.hasChanges) + try transaction.commitAndWait() - case .success(let hasChanges): - XCTAssertTrue(hasChanges) XCTAssertEqual(stack.fetchCount(From("Config1")), 1) XCTAssertEqual(stack.fetchCount(From(nil)), 0) @@ -909,8 +977,9 @@ final class TransactionTests: BaseTestCase { XCTAssertEqual(object?.testString, "string1") XCTAssertEqual(object?.testNumber, 100) XCTAssertEqual(object?.testDate, testDate) + } + catch { - default: XCTFail() } } @@ -925,10 +994,11 @@ final class TransactionTests: BaseTestCase { object.testNumber = 200 object.testDate = Date.distantFuture - switch transaction.commitAndWait() { + do { + + XCTAssertTrue(transaction.hasChanges) + try transaction.commitAndWait() - case .success(let hasChanges): - XCTAssertTrue(hasChanges) XCTAssertEqual(stack.fetchCount(From("Config1")), 1) XCTAssertEqual(stack.fetchCount(From(nil)), 0) @@ -938,8 +1008,9 @@ final class TransactionTests: BaseTestCase { XCTAssertEqual(object?.testString, "string1_edit") XCTAssertEqual(object?.testNumber, 200) XCTAssertEqual(object?.testDate, Date.distantFuture) + } + catch { - default: XCTFail() } } @@ -948,15 +1019,16 @@ final class TransactionTests: BaseTestCase { let object = transaction.fetchOne(From("Config1")) transaction.delete(object) - switch transaction.commitAndWait() { + do { - case .success(let hasChanges): - XCTAssertTrue(hasChanges) + XCTAssertTrue(transaction.hasChanges) + try transaction.commitAndWait() XCTAssertEqual(stack.fetchCount(From("Config1")), 0) XCTAssertEqual(stack.fetchCount(From(nil)), 0) + } + catch { - default: XCTFail() } } @@ -995,12 +1067,13 @@ final class TransactionTests: BaseTestCase { object.testNumber = 100 object.testDate = testDate - switch transaction.commitAndWait() { + do { - case .success(true): - break + XCTAssertTrue(transaction.hasChanges) + try transaction.commitAndWait() + } + catch { - default: XCTFail() } } diff --git a/Sources/Convenience/NSManagedObject+Convenience.swift b/Sources/Convenience/NSManagedObject+Convenience.swift index 5900631..c5a7291 100644 --- a/Sources/Convenience/NSManagedObject+Convenience.swift +++ b/Sources/Convenience/NSManagedObject+Convenience.swift @@ -33,7 +33,7 @@ public extension NSManagedObject { /** Exposes a `FetchableSource` that can fetch sibling objects of this `NSManagedObject` instance. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the obejct's parent is already deallocated. - - Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `fetchSource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `FetchableSource.internalContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time. + - Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `fetchSource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `FetchableSource.unsafeContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time. - returns: a `FetchableSource` that can fetch sibling objects of this `NSManagedObject` instance. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the object's parent is already deallocated. */ @nonobjc @@ -56,7 +56,7 @@ public extension NSManagedObject { /** Exposes a `QueryableSource` that can query attributes and aggregate values. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the obejct's parent is already deallocated. - - Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `querySource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `QueryableSource.internalContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time. + - Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `querySource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `QueryableSource.unsafeContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time. - returns: a `QueryableSource` that can query attributes and aggregate values. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the object's parent is already deallocated. */ @nonobjc diff --git a/Sources/CoreStoreError.swift b/Sources/CoreStoreError.swift index 8a4a917..fc7a79b 100644 --- a/Sources/CoreStoreError.swift +++ b/Sources/CoreStoreError.swift @@ -288,10 +288,19 @@ public extension NSError { internal var isCoreDataMigrationError: Bool { - let code = self.code - return (code == NSPersistentStoreIncompatibleVersionHashError - || code == NSMigrationMissingSourceModelError - || code == NSMigrationError) - && self.domain == NSCocoaErrorDomain + guard self.domain == CocoaError.errorDomain else { + + return false + } + switch CocoaError.Code(rawValue: self.code) { + + case CocoaError.Code.persistentStoreIncompatibleVersionHash, + CocoaError.Code.migrationMissingSourceModel, + CocoaError.Code.migration: + return true + + default: + return false + } } } diff --git a/Sources/Fetching and Querying/BaseDataTransaction+Querying.swift b/Sources/Fetching and Querying/BaseDataTransaction+Querying.swift index 761e4d6..5f40738 100644 --- a/Sources/Fetching and Querying/BaseDataTransaction+Querying.swift +++ b/Sources/Fetching and Querying/BaseDataTransaction+Querying.swift @@ -359,8 +359,17 @@ extension BaseDataTransaction: FetchableSource, QueryableSource { /** The internal `NSManagedObjectContext` managed by this instance. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases. */ - public func internalContext() -> NSManagedObjectContext { + public func unsafeContext() -> NSManagedObjectContext { return self.context } + + + // MARK: Deprecated + + @available(*, deprecated: 4.0.0, renamed: "unsafeContext()") + public func internalContext() -> NSManagedObjectContext { + + return self.unsafeContext() + } } diff --git a/Sources/Fetching and Querying/DataStack+Querying.swift b/Sources/Fetching and Querying/DataStack+Querying.swift index 7a83938..c7cba3a 100644 --- a/Sources/Fetching and Querying/DataStack+Querying.swift +++ b/Sources/Fetching and Querying/DataStack+Querying.swift @@ -322,8 +322,17 @@ extension DataStack: FetchableSource, QueryableSource { /** The internal `NSManagedObjectContext` managed by this instance. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases. */ - public func internalContext() -> NSManagedObjectContext { + public func unsafeContext() -> NSManagedObjectContext { return self.mainContext } + + + // MARK: Deprecated + + @available(*, deprecated: 4.0.0, renamed: "unsafeContext()") + public func internalContext() -> NSManagedObjectContext { + + return self.unsafeContext() + } } diff --git a/Sources/Fetching and Querying/FetchableSource.swift b/Sources/Fetching and Querying/FetchableSource.swift index 0252d36..deb667c 100644 --- a/Sources/Fetching and Querying/FetchableSource.swift +++ b/Sources/Fetching and Querying/FetchableSource.swift @@ -159,5 +159,11 @@ public protocol FetchableSource: class { /** The internal `NSManagedObjectContext` managed by this `FetchableSource`. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases. */ + func unsafeContext() -> NSManagedObjectContext + + + // MARK: Deprecated + + @available(*, deprecated: 4.0.0, renamed: "unsafeContext()") func internalContext() -> NSManagedObjectContext } diff --git a/Sources/Fetching and Querying/QueryableSource.swift b/Sources/Fetching and Querying/QueryableSource.swift index b7d5482..ba7f49b 100644 --- a/Sources/Fetching and Querying/QueryableSource.swift +++ b/Sources/Fetching and Querying/QueryableSource.swift @@ -85,5 +85,11 @@ public protocol QueryableSource: class { /** The internal `NSManagedObjectContext` managed by this `QueryableSource`. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases. */ + func unsafeContext() -> NSManagedObjectContext + + + // MARK: Deprecated + + @available(*, deprecated: 4.0.0, renamed: "unsafeContext()") func internalContext() -> NSManagedObjectContext } diff --git a/Sources/Importing/ImportableObject.swift b/Sources/Importing/ImportableObject.swift index f9eafcb..fdb9e0d 100644 --- a/Sources/Importing/ImportableObject.swift +++ b/Sources/Importing/ImportableObject.swift @@ -73,7 +73,7 @@ public protocol ImportableObject: class, NSObjectProtocol, AnyObject { func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) throws - // MARK: Deprecated + // MARK: Obsolete (`deprecated` only for reference, please use new methods) @available(*, deprecated: 3.0.0, renamed: "shouldInsert(from:in:)") static func shouldInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool @@ -93,13 +93,15 @@ public extension ImportableObject { } - // MARK: Deprecated + // MARK: Obsolete + @available(*, obsoleted: 4.0.0, renamed: "shouldInsert(from:in:)") static func shouldInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool { return Self.shouldInsert(from: source, in: transaction) } + @available(*, obsoleted: 4.0.0, renamed: "didInsert(from:in:)") func didInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) throws { try self.didInsert(from: source, in: transaction) diff --git a/Sources/Importing/ImportableUniqueObject.swift b/Sources/Importing/ImportableUniqueObject.swift index 2ae1091..ac3776c 100644 --- a/Sources/Importing/ImportableUniqueObject.swift +++ b/Sources/Importing/ImportableUniqueObject.swift @@ -116,7 +116,7 @@ public protocol ImportableUniqueObject: ImportableObject { func update(from source: ImportSource, in transaction: BaseDataTransaction) throws - // MARK: Deprecated + // MARK: Obsolete (`deprecated` only for reference, please use new methods) @available(*, deprecated: 3.0.0, renamed: "shouldInsert(from:in:)") static func shouldInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool @@ -155,28 +155,33 @@ public extension ImportableUniqueObject { } - // MARK: Deprecated + // MARK: Obsolete + @available(*, obsoleted: 4.0.0, renamed: "shouldInsert(from:in:)") static func shouldInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool { return Self.shouldInsert(from: source, in: transaction) } + @available(*, obsoleted: 4.0.0, renamed: "shouldUpdate(from:in:)") static func shouldUpdateFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool { return Self.shouldUpdate(from: source, in: transaction) } + @available(*, obsoleted: 4.0.0, renamed: "uniqueID(from:in:)") static func uniqueIDFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> UniqueIDType? { return try Self.uniqueID(from: source, in: transaction) } + @available(*, obsoleted: 4.0.0, renamed: "didInsert(from:in:)") func didInsertFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) throws { try self.didInsert(from: source, in: transaction) } + @available(*, obsoleted: 4.0.0, renamed: "update(from:in:)") func updateFromImportSource(_ source: ImportSource, inTransaction transaction: BaseDataTransaction) throws { try self.update(from: source, in: transaction) diff --git a/Sources/Internal/DispatchQueue+CoreStore.swift b/Sources/Internal/DispatchQueue+CoreStore.swift index 23c9009..0753e04 100644 --- a/Sources/Internal/DispatchQueue+CoreStore.swift +++ b/Sources/Internal/DispatchQueue+CoreStore.swift @@ -54,9 +54,14 @@ internal extension DispatchQueue { ) } - @nonobjc @inline(__always) + @nonobjc internal func cs_isCurrentExecutionContext() -> Bool { + enum Static { + + static let specificKey = DispatchSpecificKey() + } + let specific = ObjectIdentifier(self) self.setSpecific(key: Static.specificKey, value: specific) @@ -89,9 +94,4 @@ internal extension DispatchQueue { // MARK: Private - - private enum Static { - - static let specificKey = DispatchSpecificKey() - } } diff --git a/Sources/Internal/NSManagedObjectContext+Querying.swift b/Sources/Internal/NSManagedObjectContext+Querying.swift index 0be42c2..12934c1 100644 --- a/Sources/Internal/NSManagedObjectContext+Querying.swift +++ b/Sources/Internal/NSManagedObjectContext+Querying.swift @@ -290,10 +290,19 @@ extension NSManagedObjectContext: FetchableSource, QueryableSource { // MARK: FetchableSource, QueryableSource @nonobjc - public func internalContext() -> NSManagedObjectContext { + public func unsafeContext() -> NSManagedObjectContext { return self } + + + // MARK: Deprecated + + @available(*, deprecated: 4.0.0, renamed: "unsafeContext()") + public func internalContext() -> NSManagedObjectContext { + + return self.unsafeContext() + } } diff --git a/Sources/Internal/NSManagedObjectContext+Setup.swift b/Sources/Internal/NSManagedObjectContext+Setup.swift index c624cab..57e3ec1 100644 --- a/Sources/Internal/NSManagedObjectContext+Setup.swift +++ b/Sources/Internal/NSManagedObjectContext+Setup.swift @@ -82,7 +82,7 @@ internal extension NSManagedObjectContext { for objectID in updatedObjectIDs { - context?.object(with: objectID).willAccessValue(forKey: nil) + context?.registeredObject(for: objectID)?.willAccessValue(forKey: nil) } } context?.mergeChanges(fromContextDidSave: note) diff --git a/Sources/Internal/NSManagedObjectContext+Transaction.swift b/Sources/Internal/NSManagedObjectContext+Transaction.swift index 0b22633..f25f81d 100644 --- a/Sources/Internal/NSManagedObjectContext+Transaction.swift +++ b/Sources/Internal/NSManagedObjectContext+Transaction.swift @@ -140,17 +140,15 @@ internal extension NSManagedObjectContext { } @nonobjc - internal func saveSynchronously(waitForMerge: Bool) -> SaveResult { + internal func saveSynchronously(waitForMerge: Bool) -> (hasChanges: Bool, error: CoreStoreError?) { - var result = SaveResult(hasChanges: false) - + var result: (hasChanges: Bool, error: CoreStoreError?) = (false, nil) self.performAndWait { guard self.hasChanges else { return } - do { self.isSavingSynchronously = waitForMerge @@ -164,32 +162,24 @@ internal extension NSManagedObjectContext { saveError, "Failed to save \(cs_typeName(NSManagedObjectContext.self))." ) - result = SaveResult(saveError) + result = (true, saveError) return } - if let parentContext = self.parent, self.shouldCascadeSavesToParent { - switch parentContext.saveSynchronously(waitForMerge: waitForMerge) { - - case .success: - result = SaveResult(hasChanges: true) - - case .failure(let error): - result = SaveResult(error) - } + let (_, error) = parentContext.saveSynchronously(waitForMerge: waitForMerge) + result = (true, error) } else { - result = SaveResult(hasChanges: true) + result = (true, nil) } } - return result } @nonobjc - internal func saveAsynchronouslyWithCompletion(_ completion: @escaping ((_ result: SaveResult) -> Void) = { _ in }) { + internal func saveAsynchronouslyWithCompletion(_ completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { _ in }) { self.perform { @@ -197,11 +187,10 @@ internal extension NSManagedObjectContext { DispatchQueue.main.async { - completion(SaveResult(hasChanges: false)) + completion(false, nil) } return } - do { self.isSavingSynchronously = false @@ -216,21 +205,23 @@ internal extension NSManagedObjectContext { "Failed to save \(cs_typeName(NSManagedObjectContext.self))." ) DispatchQueue.main.async { - - completion(SaveResult(saveError)) + + completion(true, saveError) } return } - if self.shouldCascadeSavesToParent, let parentContext = self.parent { - parentContext.saveAsynchronouslyWithCompletion(completion) + parentContext.saveAsynchronouslyWithCompletion { (_, error) in + + completion(true, error) + } } else { DispatchQueue.main.async { - - completion(SaveResult(hasChanges: true)) + + completion(true, nil) } } } diff --git a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift index b5413a3..1eba26e 100644 --- a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift +++ b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift @@ -48,8 +48,7 @@ extension AsynchronousDataTransaction: CustomDebugStringConvertible, CoreStoreDe ("context", self.context), ("supportsUndo", self.supportsUndo), ("bypassesQueueing", self.bypassesQueueing), - ("isCommitted", self.isCommitted), - ("result", self.result as Any) + ("isCommitted", self.isCommitted) ) } } @@ -614,6 +613,7 @@ extension OrderBy: CustomDebugStringConvertible, CoreStoreDebugStringConvertible // MARK: - SaveResult +@available(*, deprecated: 4.0.0, message: "Use the new DataStack.perform(asynchronous:...) and DataStack.perform(synchronous:...) family of APIs") extension SaveResult: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { // MARK: CustomDebugStringConvertible @@ -824,8 +824,7 @@ extension SynchronousDataTransaction: CustomDebugStringConvertible, CoreStoreDeb ("context", self.context), ("supportsUndo", self.supportsUndo), ("bypassesQueueing", self.bypassesQueueing), - ("isCommitted", self.isCommitted), - ("result", self.result as Any) + ("isCommitted", self.isCommitted) ) } } diff --git a/Sources/Logging/CoreStore+Logging.swift b/Sources/Logging/CoreStore+Logging.swift index 6102cf8..c223b77 100644 --- a/Sources/Logging/CoreStore+Logging.swift +++ b/Sources/Logging/CoreStore+Logging.swift @@ -38,6 +38,7 @@ public extension CoreStore { // MARK: Internal + @inline(__always) internal static func log(_ level: LogLevel, message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) { self.logger.log( @@ -49,6 +50,7 @@ public extension CoreStore { ) } + @inline(__always) internal static func log(_ error: CoreStoreError, _ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) { self.logger.log( @@ -60,6 +62,7 @@ public extension CoreStore { ) } + @inline(__always) internal static func assert( _ condition: @autoclosure () -> Bool, _ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) { self.logger.assert( @@ -71,6 +74,7 @@ public extension CoreStore { ) } + @inline(__always) internal static func abort(_ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) -> Never { self.logger.abort( diff --git a/Sources/ObjectiveC/CSAsynchronousDataTransaction.swift b/Sources/ObjectiveC/CSAsynchronousDataTransaction.swift index aad2458..aaaa017 100644 --- a/Sources/ObjectiveC/CSAsynchronousDataTransaction.swift +++ b/Sources/ObjectiveC/CSAsynchronousDataTransaction.swift @@ -43,29 +43,22 @@ public final class CSAsynchronousDataTransaction: CSBaseDataTransaction { - parameter completion: the block executed after the save completes. Success or failure is reported by the `CSSaveResult` argument of the block. */ @objc - public func commitWithCompletion(_ completion: ((_ result: CSSaveResult) -> Void)?) { + public func commitWithSuccess(_ success: (() -> Void)?, failure: ((CSError) -> Void)?) { - self.bridgeToSwift.commit { (result) in + CoreStore.assert( + self.bridgeToSwift.transactionQueue.cs_isCurrentExecutionContext(), + "Attempted to commit a \(cs_typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.bridgeToSwift.isCommitted, + "Attempted to commit a \(cs_typeName(self)) more than once." + ) + self.bridgeToSwift.autoCommit { (result) in - completion?(result.bridgeToObjectiveC) - } - } - - /** - Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `-commitWithCompletion:` method was already called once. - - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - - returns: a `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously - */ - @objc - @discardableResult - public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? { - - return bridge { - - self.bridgeToSwift.beginSynchronous { (transaction) in + switch result { - closure(transaction.bridgeToObjectiveC) + case (_, nil): success?() + case (_, let error?): failure?(error.bridgeToObjectiveC) } } } @@ -159,6 +152,52 @@ public final class CSAsynchronousDataTransaction: CSBaseDataTransaction { super.init(swiftValue as! AsynchronousDataTransaction) } + + + // MARK: Deprecated + + /** + Saves the transaction changes. This method should not be used after the `-commitWithCompletion:` method was already called once. + + - parameter completion: the block executed after the save completes. Success or failure is reported by the `CSSaveResult` argument of the block. + */ + @available(*, deprecated: 4.0.0, message: "Use the new -[CSAsynchronousDataTransaction commitWithSuccess:failure:] method.") + @objc + public func commitWithCompletion(_ completion: ((_ result: CSSaveResult) -> Void)?) { + + CoreStore.assert( + self.bridgeToSwift.transactionQueue.cs_isCurrentExecutionContext(), + "Attempted to commit a \(cs_typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.bridgeToSwift.isCommitted, + "Attempted to commit a \(cs_typeName(self)) more than once." + ) + self.bridgeToSwift.commit { (result) in + + completion?(result.bridgeToObjectiveC) + } + } + + /** + Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `-commitWithCompletion:` method was already called once. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ + @available(*, deprecated: 4.0.0, message: "Secondary tasks spawned from CSAsynchronousDataTransactions and CSSynchronousDataTransactions are no longer supported. ") + @objc + @discardableResult + public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? { + + return bridge { + + self.bridgeToSwift.beginSynchronous { (transaction) in + + closure(transaction.bridgeToObjectiveC) + } + } + } } diff --git a/Sources/ObjectiveC/CSCoreStore+Transaction.swift b/Sources/ObjectiveC/CSCoreStore+Transaction.swift index 4c2928b..ce8f0fc 100644 --- a/Sources/ObjectiveC/CSCoreStore+Transaction.swift +++ b/Sources/ObjectiveC/CSCoreStore+Transaction.swift @@ -38,10 +38,7 @@ public extension CSCoreStore { @objc public static func beginAsynchronous(_ closure: @escaping (_ transaction: CSAsynchronousDataTransaction) -> Void) { - return CoreStore.beginAsynchronous { (transaction) in - - closure(transaction.bridgeToObjectiveC) - } + self.defaultStack.beginAsynchronous(closure) } /** @@ -51,16 +48,9 @@ public extension CSCoreStore { - returns: a `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously */ @objc - @discardableResult - public static func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? { + public static func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void, error: NSErrorPointer) -> Bool { - return bridge { - - CoreStore.beginSynchronous { (transaction) in - - closure(transaction.bridgeToObjectiveC) - } - } + return self.defaultStack.beginSynchronous(closure, error: error) } /** @@ -101,4 +91,21 @@ public extension CSCoreStore { CoreStore.refreshAndMergeAllObjects() } + + + // MARK: Deprecated + + /** + Using the `defaultStack`, begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ + @available(*, deprecated: 4.0.0, message: "Use the new +[CSCoreStore beginSynchronous:error:] API that reports failure using an error instance.") + @objc + @discardableResult + public static func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? { + + return self.defaultStack.beginSynchronous(closure) + } } diff --git a/Sources/ObjectiveC/CSDataStack+Transaction.swift b/Sources/ObjectiveC/CSDataStack+Transaction.swift index f0d35d5..63d4ef8 100644 --- a/Sources/ObjectiveC/CSDataStack+Transaction.swift +++ b/Sources/ObjectiveC/CSDataStack+Transaction.swift @@ -38,27 +38,57 @@ public extension CSDataStack { @objc public func beginAsynchronous(_ closure: @escaping (_ transaction: CSAsynchronousDataTransaction) -> Void) { - return self.bridgeToSwift.beginAsynchronous { (transaction) in - - closure(transaction.bridgeToObjectiveC) - } + self.bridgeToSwift.perform( + asynchronous: { (transaction) in + + let csTransaction = transaction.bridgeToObjectiveC + closure(csTransaction) + if !transaction.isCommitted && transaction.hasChanges { + + CoreStore.log( + .warning, + message: "The closure for the \(cs_typeName(csTransaction)) completed without being committed. All changes made within the transaction were discarded." + ) + } + try transaction.cancel() + }, + completion: { _ in } + ) } /** Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - - returns: a `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + - parameter error: the `CSError` pointer that indicates the reason in case of an failure + - returns: `YES` if the commit succeeded, `NO` if the commit failed. If `NO`, the `error` argument will hold error information. */ @objc - @discardableResult - public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? { + public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void, error: NSErrorPointer) -> Bool { - return bridge { + return bridge(error) { - self.bridgeToSwift.beginSynchronous { (transaction) in + do { - closure(transaction.bridgeToObjectiveC) + try self.bridgeToSwift.perform( + synchronous: { (transaction) in + + let csTransaction = transaction.bridgeToObjectiveC + closure(csTransaction) + if !transaction.isCommitted && transaction.hasChanges { + + CoreStore.log( + .warning, + message: "The closure for the \(cs_typeName(csTransaction)) completed without being committed. All changes made within the transaction were discarded." + ) + } + try transaction.cancel() + } + ) + } + catch CoreStoreError.userCancelled { + + return } } } @@ -101,4 +131,27 @@ public extension CSDataStack { self.bridgeToSwift.refreshAndMergeAllObjects() } + + + // MARK: Deprecated + + /** + Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ + @available(*, deprecated: 4.0.0, message: "Use the new -[CSDataStack beginSynchronous:error:] API that reports failure using an error instance.") + @objc + @discardableResult + public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? { + + return bridge { + + self.bridgeToSwift.beginSynchronous { (transaction) in + + closure(transaction.bridgeToObjectiveC) + } + } + } } diff --git a/Sources/ObjectiveC/CSSaveResult.swift b/Sources/ObjectiveC/CSSaveResult.swift index 3fe7063..5a955fe 100644 --- a/Sources/ObjectiveC/CSSaveResult.swift +++ b/Sources/ObjectiveC/CSSaveResult.swift @@ -34,6 +34,7 @@ import CoreData - SeeAlso: `SaveResult` */ +@available(*, deprecated: 4.0.0, message: "Use APIs that report failures with `CSError`s instead.") @objc public final class CSSaveResult: NSObject, CoreStoreObjectiveCType { @@ -173,6 +174,7 @@ public final class CSSaveResult: NSObject, CoreStoreObjectiveCType { // MARK: - SaveResult +@available(*, deprecated: 4.0.0, message: "Use the new DataStack.perform(asynchronous:...) and DataStack.perform(synchronous:...) family of APIs") extension SaveResult: CoreStoreSwiftType { // MARK: CoreStoreSwiftType diff --git a/Sources/ObjectiveC/CSSynchronousDataTransaction.swift b/Sources/ObjectiveC/CSSynchronousDataTransaction.swift index 812964d..792ac91 100644 --- a/Sources/ObjectiveC/CSSynchronousDataTransaction.swift +++ b/Sources/ObjectiveC/CSSynchronousDataTransaction.swift @@ -38,34 +38,19 @@ import CoreData public final class CSSynchronousDataTransaction: CSBaseDataTransaction { /** - Saves the transaction changes and waits for completion synchronously. This method should not be used after the `-commitAndWait` method was already called once. + Saves the transaction changes and waits for completion synchronously. This method should not be used after the `-commitAndWaitWithError:` method was already called once. - - returns: a `CSSaveResult` containing the success or failure information + - parameter error: the `CSError` pointer that indicates the reason in case of an failure + - returns: `YES` if the commit succeeded, `NO` if the commit failed. If `NO`, the `error` argument will hold error information. */ @objc - public func commitAndWait() -> CSSaveResult { + public func commitAndWait(error: NSErrorPointer) -> Bool { - return bridge { + return bridge(error) { - self.bridgeToSwift.commitAndWait() - } - } - - /** - Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `-commitAndWait` method was already called once. - - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - - returns: a `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously - */ - @objc - @discardableResult - public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? { - - return bridge { - - self.bridgeToSwift.beginSynchronous { (transaction) in + if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) { - closure(transaction.bridgeToObjectiveC) + throw error } } } @@ -158,6 +143,44 @@ public final class CSSynchronousDataTransaction: CSBaseDataTransaction { super.init(swiftValue as! SynchronousDataTransaction) } + + + // MARK: Deprecated + + /** + Saves the transaction changes and waits for completion synchronously. This method should not be used after the `-commitAndWait` method was already called once. + + - returns: a `CSSaveResult` containing the success or failure information + */ + @available(*, deprecated: 4.0.0, message: "Use the new -[CSSynchronousDataTransaction commitAndWaitWithError:] method") + @objc + public func commitAndWait() -> CSSaveResult { + + return bridge { + + self.bridgeToSwift.commitAndWait() + } + } + + /** + Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `-commitAndWait` method was already called once. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `CSSaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ + @available(*, deprecated: 4.0.0, message: "Secondary tasks spawned from CSAsynchronousDataTransactions and CSSynchronousDataTransactions are no longer supported. ") + @objc + @discardableResult + public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void) -> CSSaveResult? { + + return bridge { + + self.bridgeToSwift.beginSynchronous { (transaction) in + + closure(transaction.bridgeToObjectiveC) + } + } + } } diff --git a/Sources/ObjectiveC/CSUnsafeDataTransaction.swift b/Sources/ObjectiveC/CSUnsafeDataTransaction.swift index f42388a..6bdcf97 100644 --- a/Sources/ObjectiveC/CSUnsafeDataTransaction.swift +++ b/Sources/ObjectiveC/CSUnsafeDataTransaction.swift @@ -36,32 +36,43 @@ import CoreData */ @objc public final class CSUnsafeDataTransaction: CSBaseDataTransaction { - /** Saves the transaction changes asynchronously. For a `CSUnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread. - - parameter completion: the block executed after the save completes. Success or failure is reported by the `CSSaveResult` argument of the block. + - parameter completion: the block executed after the save completes. Success or failure is reported by the `error` argument of the block. */ @objc - public func commit(_ completion: ((_ result: CSSaveResult) -> Void)?) { + public func commitWithSuccess(_ success: (() -> Void)?, _ failure: ((CSError) -> Void)?) { - self.bridgeToSwift.commit { (result) in + self.bridgeToSwift.context.saveAsynchronouslyWithCompletion { (_, error) in - completion?(result.bridgeToObjectiveC) + if let error = error { + + failure?(error.bridgeToObjectiveC) + } + else { + + success?() + } + withExtendedLifetime(self, {}) } } /** Saves the transaction changes and waits for completion synchronously. For a `CSUnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread. - - returns: a `CSSaveResult` containing the success or failure information + - parameter error: the `CSError` pointer that indicates the reason in case of an failure + - returns: `YES` if the commit succeeded, `NO` if the commit failed. If `NO`, the `error` argument will hold error information. */ @objc - public func commitAndWait() -> CSSaveResult { + public func commitAndWait(error: NSErrorPointer) -> Bool { - return bridge { + return bridge(error) { - self.bridgeToSwift.commitAndWait() + if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) { + + throw error + } } } @@ -156,7 +167,7 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction { - that all saves will be done either through the `CSUnsafeDataTransaction`'s `-commit:` or `-commitAndWait` method, or by calling `-save:` manually on the context, its parent, and all other ancestor contexts if there are any. */ @objc - public var internalContext: NSManagedObjectContext { + public func unsafeContext() -> NSManagedObjectContext { return self.bridgeToSwift.context } @@ -188,6 +199,58 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction { super.init(swiftValue as! UnsafeDataTransaction) } + + + // MARK: Deprecated + + @available(*, deprecated: 4.0.0, renamed: "unsafeContext()") + @objc + public var internalContext: NSManagedObjectContext { + + return self.bridgeToSwift.context + } + + /** + Saves the transaction changes asynchronously. For a `CSUnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread. + + - parameter completion: the block executed after the save completes. Success or failure is reported by the `CSSaveResult` argument of the block. + */ + @available(*, deprecated: 4.0.0, message: "Use the new -[CSUnsafeDataTransaction commitWithSuccess:failure:] method") + @objc + public func commit(_ completion: ((_ result: CSSaveResult) -> Void)?) { + + self.bridgeToSwift.context.saveAsynchronouslyWithCompletion { (hasChanges, error) in + + if let error = error { + + completion?(SaveResult(error).bridgeToObjectiveC) + } + else { + + completion?(SaveResult(hasChanges: hasChanges).bridgeToObjectiveC) + } + withExtendedLifetime(self, {}) + } + } + + /** + Saves the transaction changes and waits for completion synchronously. For a `CSUnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread. + + - returns: a `CSSaveResult` containing the success or failure information + */ + @available(*, deprecated: 4.0.0, message: "Use the new -[CSUnsafeDataTransaction commitAndWaitWithError:] method") + @objc + public func commitAndWait() -> CSSaveResult { + + return bridge { () -> SaveResult in + + switch self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) { + + case (let hasChanges, nil): return SaveResult(hasChanges: hasChanges) + case (_, let error?): return SaveResult(error) + } + } + } } diff --git a/Sources/Observing/ListMonitor.swift b/Sources/Observing/ListMonitor.swift index e9272a9..200c0fa 100644 --- a/Sources/Observing/ListMonitor.swift +++ b/Sources/Observing/ListMonitor.swift @@ -202,7 +202,7 @@ public final class ListMonitor: Hashable { !self.isPendingRefetch || Thread.isMainThread, "Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress." ) - return self.fetchedResultsController.dynamicCast().fetchedObjects ?? [] + return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController).fetchedObjects ?? [] } /** @@ -213,7 +213,7 @@ public final class ListMonitor: Hashable { */ public func objectsInSection(_ section: Int) -> [T] { - return (self.sectionInfoAtIndex(section).objects as? [T]) ?? [] + return (self.sectionInfoAtIndex(section).objects as! [T]?) ?? [] } /** @@ -224,7 +224,7 @@ public final class ListMonitor: Hashable { */ public func objectsInSection(safeSectionIndex section: Int) -> [T]? { - return (self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as? [T]) ?? [] + return self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as! [T]? } /** @@ -371,7 +371,7 @@ public final class ListMonitor: Hashable { !self.isPendingRefetch || Thread.isMainThread, "Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress." ) - return (self.fetchedResultsController.dynamicCast().fetchedObjects ?? []).index(of: object) + return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController).fetchedObjects?.index(of: object) } /** diff --git a/Sources/Setup/CoreStore+Setup.swift b/Sources/Setup/CoreStore+Setup.swift index d4346de..cbf1d94 100644 --- a/Sources/Setup/CoreStore+Setup.swift +++ b/Sources/Setup/CoreStore+Setup.swift @@ -155,12 +155,9 @@ public extension CoreStore { } - // MARK: Deprecated + // MARK: Obsolete - /** - Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `defaultStack`'s model. - */ - @available(*, deprecated: 3.0.0, renamed: "entityDescription(for:)") + @available(*, obsoleted: 4.0.0, renamed: "entityDescription(for:)") public static func entityDescriptionForType(_ type: NSManagedObject.Type) -> NSEntityDescription? { return self.entityDescription(for: type) diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index d79ed5d..660278d 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -529,15 +529,15 @@ public final class DataStack: Equatable { } - // MARK: Deprecated + // MARK: Obsolete - @available(*, deprecated: 3.0.0, renamed: "entityDescription(for:)") + @available(*, obsoleted: 4.0.0, renamed: "entityDescription(for:)") public func entityDescriptionForType(_ type: NSManagedObject.Type) -> NSEntityDescription? { return self.entityDescription(for: type) } - @available(*, deprecated: 3.0.0, renamed: "objectID(forURIRepresentation:)") + @available(*, obsoleted: 4.0.0, renamed: "objectID(forURIRepresentation:)") public func objectIDForURIRepresentation(_ url: URL) -> NSManagedObjectID? { return self.objectID(forURIRepresentation: url) diff --git a/Sources/Transactions/AsynchronousDataTransaction.swift b/Sources/Transactions/AsynchronousDataTransaction.swift index f9228e7..396a3e0 100644 --- a/Sources/Transactions/AsynchronousDataTransaction.swift +++ b/Sources/Transactions/AsynchronousDataTransaction.swift @@ -34,63 +34,51 @@ import CoreData */ public final class AsynchronousDataTransaction: BaseDataTransaction { + // MARK: - Result + + public enum Result { + + case success(userInfo: T) + case failure(error: CoreStoreError) + + public var boolValue: Bool { + + switch self { + + case .success: return true + case .failure: return false + } + } + + + // MARK: Internal + + internal init(userInfo: T) { + + self = .success(userInfo: userInfo) + } + + internal init(error: CoreStoreError) { + + self = .failure(error: error) + } + } + + + // MARK: - + + /** + Cancels a transaction by throwing `CoreStoreError.userCancelled`. + ``` + try transaction.cancel() + ``` + - Important: Never use `try?` or `try!` on a `cancel()` call. Always use `try`. Using `try?` will swallow the cancellation and the transaction will proceed to commit as normal. Using `try!` will crash the app as `cancel()` will *always* throw an error. + */ public func cancel() throws -> Never { throw CoreStoreError.userCancelled } - /** - Saves the transaction changes. This method should not be used after the `commit()` method was already called once. - - - parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block. - */ - public func commit(_ completion: @escaping (_ result: SaveResult) -> Void = { _ in }) { - - CoreStore.assert( - self.transactionQueue.cs_isCurrentExecutionContext(), - "Attempted to commit a \(cs_typeName(self)) outside its designated queue." - ) - CoreStore.assert( - !self.isCommitted, - "Attempted to commit a \(cs_typeName(self)) more than once." - ) - - self.isCommitted = true - let group = DispatchGroup() - group.enter() - self.context.saveAsynchronouslyWithCompletion { (result) -> Void in - - self.result = result - completion(result) - group.leave() - } - group.wait() - } - - /** - Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once. - - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously - */ - @discardableResult - public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { - - CoreStore.assert( - self.transactionQueue.cs_isCurrentExecutionContext(), - "Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue." - ) - CoreStore.assert( - !self.isCommitted, - "Attempted to begin a child transaction from an already committed \(cs_typeName(self))." - ) - - return SynchronousDataTransaction( - mainContext: self.context, - queue: self.childTransactionQueue, - closure: closure).performAndWait() - } - // MARK: BaseDataTransaction @@ -193,47 +181,93 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { // MARK: Internal - internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue, closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) { - - self.closure = closure + internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue) { super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false) } - internal func perform() { + internal func autoCommit(_ completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void) { - self.transactionQueue.async { + self.isCommitted = true + let group = DispatchGroup() + group.enter() + self.context.saveAsynchronouslyWithCompletion { (result) -> Void in - self.closure(self) - if !self.isCommitted && self.hasChanges { + completion(result.0, result.1) + self.result = result + group.leave() + } + group.wait() + } + + + // MARK: Deprecated + + /** + Saves the transaction changes. This method should not be used after the `commit()` method was already called once. + + - parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block. + */ + @available(*, deprecated: 4.0.0, message: "Use the new auto-commiting methods `DataStack.perform(asynchronous:completion:)` or `DataStack.perform(asynchronous:success:failure:)`. Please read the documentation on the behavior of the new methods.") + public func commit(_ completion: @escaping (_ result: SaveResult) -> Void = { _ in }) { + + CoreStore.assert( + self.transactionQueue.cs_isCurrentExecutionContext(), + "Attempted to commit a \(cs_typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to commit a \(cs_typeName(self)) more than once." + ) + self.autoCommit { (result) in + + switch result { - CoreStore.log( - .warning, - message: "The closure for the \(cs_typeName(self)) completed without being committed. All changes made within the transaction were discarded." - ) + case (let hasChanges, nil): completion(SaveResult(hasChanges: hasChanges)) + case (_, let error?): completion(SaveResult(error)) } } } - internal func performAndWait() -> SaveResult? { + /** + Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ + @available(*, deprecated: 4.0.0, message: "Secondary tasks spawned from AsynchronousDataTransactions and SynchronousDataTransactions are no longer supported. ") + @discardableResult + public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { - self.transactionQueue.sync { + CoreStore.assert( + self.transactionQueue.cs_isCurrentExecutionContext(), + "Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to begin a child transaction from an already committed \(cs_typeName(self))." + ) + let childTransaction = SynchronousDataTransaction( + mainContext: self.context, + queue: self.childTransactionQueue + ) + childTransaction.transactionQueue.cs_sync { - self.closure(self) + closure(childTransaction) - if !self.isCommitted && self.hasChanges { + if !childTransaction.isCommitted && childTransaction.hasChanges { CoreStore.log( .warning, - message: "The closure for the \(cs_typeName(self)) completed without being committed. All changes made within the transaction were discarded." + message: "The closure for the \(cs_typeName(childTransaction)) completed without being committed. All changes made within the transaction were discarded." ) } } - return self.result + switch childTransaction.result { + + case nil: return nil + case (let hasChanges, nil)?: return SaveResult(hasChanges: hasChanges) + case (_, let error?)?: return SaveResult(error) + } } - - - // MARK: Private - - private let closure: (_ transaction: AsynchronousDataTransaction) -> Void } diff --git a/Sources/Transactions/BaseDataTransaction.swift b/Sources/Transactions/BaseDataTransaction.swift index 6b6c0ec..dc626d7 100644 --- a/Sources/Transactions/BaseDataTransaction.swift +++ b/Sources/Transactions/BaseDataTransaction.swift @@ -455,7 +455,7 @@ public /*abstract*/ class BaseDataTransaction { internal let supportsUndo: Bool internal let bypassesQueueing: Bool internal var isCommitted = false - internal var result: SaveResult? + internal var result: (hasChanges: Bool, error: CoreStoreError?)? internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue, supportsUndo: Bool, bypassesQueueing: Bool) { diff --git a/Sources/Transactions/CoreStore+Transaction.swift b/Sources/Transactions/CoreStore+Transaction.swift index 23b5bd0..54c79f0 100644 --- a/Sources/Transactions/CoreStore+Transaction.swift +++ b/Sources/Transactions/CoreStore+Transaction.swift @@ -31,25 +31,39 @@ import Foundation public extension CoreStore { /** - Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. + Using the `defaultStack`, performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the wrapped as `TransactionResult.success(userInfo: T)` in the `completion`'s `TransactionResult`. Any errors thrown from inside the `task` will be reported as `TransactionResult.failure(error: Error)`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`. - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - parameter completion: the closure executed after the save completes. The `TransactionResult` argument of the closure will either wrap the return value of `task`, or any uncaught errors thrown from within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`. */ - public static func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) { + public static func perform(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (AsynchronousDataTransaction.Result) -> Void) { - self.defaultStack.beginAsynchronous(closure) + self.defaultStack.perform(asynchronous: task, completion: completion) } /** - Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. + Using the `defaultStack`, performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the argument of the `success` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported in the `failure` closure. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`. - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + - parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - parameter success: the closure executed after the save succeeds. The `T` argument of the closure will be the value returned from `task`. + - parameter failure: the closure executed if the save fails or if any errors are thrown within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`. */ - @discardableResult - public static func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { + public static func perform(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) { - return self.defaultStack.beginSynchronous(closure) + self.defaultStack.perform(asynchronous: task, success: success, failure: failure) + } + + /** + Using the `defaultStack`, performs a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be rethrown from `perform(synchronous:)`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`. + + - parameter task: the synchronous non-escaping closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - parameter waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`. + - throws: a `CoreStoreError` value indicating the failure. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`. + - returns: the value returned from `task` + */ + public static func perform(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T { + + return try self.defaultStack.perform(synchronous: task, waitForAllObservers: waitForAllObservers) } /** @@ -70,4 +84,31 @@ public extension CoreStore { self.defaultStack.refreshAndMergeAllObjects() } + + + // MARK: Deprecated + + /** + Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + */ + @available(*, deprecated: 4.0.0, message: "Use the new auto-commiting methods `perform(asynchronous:completion:)` or `perform(asynchronous:success:failure:)`. Please read the documentation on the behavior of the new methods.") + public static func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) { + + self.defaultStack.beginAsynchronous(closure) + } + + /** + Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ + @available(*, deprecated: 4.0.0, message: "Use the new auto-commiting method `perform(synchronous:)`. Please read the documentation on the behavior of the new methods.") + @discardableResult + public static func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { + + return self.defaultStack.beginSynchronous(closure) + } } diff --git a/Sources/Transactions/DataStack+Transaction.swift b/Sources/Transactions/DataStack+Transaction.swift index 9b4076b..00d7518 100644 --- a/Sources/Transactions/DataStack+Transaction.swift +++ b/Sources/Transactions/DataStack+Transaction.swift @@ -31,22 +31,33 @@ import CoreData public extension DataStack { - - public func perform(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (TransactionResult) -> Void) { + /** + Performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the wrapped as `TransactionResult.success(userInfo: T)` in the `completion`'s `TransactionResult`. Any errors thrown from inside the `task` will be reported as `TransactionResult.failure(error: Error)`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`. + + - parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - parameter completion: the closure executed after the save completes. The `TransactionResult` argument of the closure will either wrap the return value of `task`, or any uncaught errors thrown from within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`. + */ + public func perform(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (AsynchronousDataTransaction.Result) -> Void) { self.perform( asynchronous: task, - success: { completion(TransactionResult(userInfo: $0)) }, - failure: { completion(TransactionResult(error: $0)) } + success: { completion(.init(userInfo: $0)) }, + failure: { completion(.init(error: $0)) } ) } + /** + Performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the argument of the `success` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported in the `failure` closure. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`. + + - parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - parameter success: the closure executed after the save succeeds. The `T` argument of the closure will be the value returned from `task`. + - parameter failure: the closure executed if the save fails or if any errors are thrown within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`. + */ public func perform(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) { let transaction = AsynchronousDataTransaction( mainContext: self.rootSavingContext, - queue: self.childTransactionQueue, - closure: { _ in } + queue: self.childTransactionQueue ) transaction.transactionQueue.cs_async { @@ -65,30 +76,40 @@ public extension DataStack { DispatchQueue.main.async { failure(.userError(error: error)) } return } - transaction.commit { (result) in + transaction.autoCommit { (_, error) in - switch result { + if let error = error { - case .success: success(userInfo) - case .failure(let error): failure(error) + failure(error) + } + else { + + success(userInfo) } } } } - public func perform(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForObserverNotifications: Bool = true) throws -> T { + /** + Performs a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be rethrown from `perform(synchronous:)`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`. + + - parameter task: the synchronous non-escaping closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - parameter waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`. + - throws: a `CoreStoreError` value indicating the failure. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`. + - returns: the value returned from `task` + */ + public func perform(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T { let transaction = SynchronousDataTransaction( mainContext: self.rootSavingContext, - queue: self.childTransactionQueue, - closure: { _ in } + queue: self.childTransactionQueue ) return try transaction.transactionQueue.cs_sync { let userInfo: T do { - userInfo = try task(transaction) + userInfo = try withoutActuallyEscaping(task, do: { try $0(transaction) }) } catch let error as CoreStoreError { @@ -98,46 +119,17 @@ public extension DataStack { throw CoreStoreError.userError(error: error) } - let result = waitForObserverNotifications - ? transaction.commitAndWait() - : transaction.commit() - switch result { + if case (_, let error?) = transaction.autoCommit(waitForMerge: waitForAllObservers) { - case .success: return userInfo - case .failure(let error): throw error + throw error + } + else { + + return userInfo } } } - - /** - Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. - - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - */ - public func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) { - - AsynchronousDataTransaction( - mainContext: self.rootSavingContext, - queue: self.childTransactionQueue, - closure: closure).perform() - } - - /** - Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. - - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously - */ - @discardableResult - public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { - - return SynchronousDataTransaction( - mainContext: self.rootSavingContext, - queue: self.childTransactionQueue, - closure: closure).performAndWait() - } - /** Begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. @@ -164,4 +156,67 @@ public extension DataStack { ) self.mainContext.refreshAndMergeAllObjects() } + + + // MARK: Deprecated + + /** + Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + */ + @available(*, deprecated: 4.0.0, message: "Use the new auto-commiting methods `perform(asynchronous:completion:)` or `perform(asynchronous:success:failure:)`. Please read the documentation on the behavior of the new methods.") + public func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) { + + let transaction = AsynchronousDataTransaction( + mainContext: self.rootSavingContext, + queue: self.childTransactionQueue + ) + transaction.transactionQueue.cs_async { + + closure(transaction) + + if !transaction.isCommitted && transaction.hasChanges { + + CoreStore.log( + .warning, + message: "The closure for the \(cs_typeName(transaction)) completed without being committed. All changes made within the transaction were discarded." + ) + } + } + } + + /** + Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ + @available(*, deprecated: 4.0.0, message: "Use the new auto-commiting method `perform(synchronous:)`. Please read the documentation on the behavior of the new methods.") + @discardableResult + public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { + + let transaction = SynchronousDataTransaction( + mainContext: self.rootSavingContext, + queue: self.childTransactionQueue + ) + transaction.transactionQueue.cs_sync { + + closure(transaction) + + if !transaction.isCommitted && transaction.hasChanges { + + CoreStore.log( + .warning, + message: "The closure for the \(cs_typeName(transaction)) completed without being committed. All changes made within the transaction were discarded." + ) + } + } + switch transaction.result { + + case nil: return nil + case (let hasChanges, nil)?: return SaveResult(hasChanges: hasChanges) + case (_, let error?)?: return SaveResult(error) + } + } } diff --git a/Sources/Transactions/SaveResult.swift b/Sources/Transactions/SaveResult.swift index 15632a5..8735818 100644 --- a/Sources/Transactions/SaveResult.swift +++ b/Sources/Transactions/SaveResult.swift @@ -57,6 +57,7 @@ import Foundation } ``` */ +@available(*, deprecated: 4.0.0, message: "Use the new DataStack.perform(asynchronous:...) and DataStack.perform(synchronous:...) family of APIs") public enum SaveResult: Hashable { /** diff --git a/Sources/Transactions/SynchronousDataTransaction.swift b/Sources/Transactions/SynchronousDataTransaction.swift index 5b63066..74c1c4d 100644 --- a/Sources/Transactions/SynchronousDataTransaction.swift +++ b/Sources/Transactions/SynchronousDataTransaction.swift @@ -34,83 +34,18 @@ import CoreData */ public final class SynchronousDataTransaction: BaseDataTransaction { + /** + Cancels a transaction by throwing `CoreStoreError.userCancelled`. + ``` + try transaction.cancel() + ``` + - Important: Never use `try?` or `try!` on a `cancel()` call. Always use `try`. Using `try?` will swallow the cancellation and the transaction will proceed to commit as normal. Using `try!` will crash the app as `cancel()` will *always* throw an error. + */ public func cancel() throws -> Never { throw CoreStoreError.userCancelled } - /** - Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once. - - Important: Unlike `SynchronousDataTransaction.commit()`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. - - - returns: a `SaveResult` containing the success or failure information - */ - public func commitAndWait() -> SaveResult { - - CoreStore.assert( - self.transactionQueue.cs_isCurrentExecutionContext(), - "Attempted to commit a \(cs_typeName(self)) outside its designated queue." - ) - CoreStore.assert( - !self.isCommitted, - "Attempted to commit a \(cs_typeName(self)) more than once." - ) - - self.isCommitted = true - - let result = self.context.saveSynchronously(waitForMerge: true) - self.result = result - return result - } - - /** - Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once. - - Important: Unlike `SynchronousDataTransaction.commitAndWait()`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. - - - returns: a `SaveResult` containing the success or failure information - */ - public func commit() -> SaveResult { - - CoreStore.assert( - self.transactionQueue.cs_isCurrentExecutionContext(), - "Attempted to commit a \(cs_typeName(self)) outside its designated queue." - ) - CoreStore.assert( - !self.isCommitted, - "Attempted to commit a \(cs_typeName(self)) more than once." - ) - - self.isCommitted = true - - let result = self.context.saveSynchronously(waitForMerge: false) - self.result = result - return result - } - - /** - Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once. - - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously - */ - @discardableResult - public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { - - CoreStore.assert( - self.transactionQueue.cs_isCurrentExecutionContext(), - "Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue." - ) - CoreStore.assert( - !self.isCommitted, - "Attempted to begin a child transaction from an already committed \(cs_typeName(self))." - ) - - return SynchronousDataTransaction( - mainContext: self.context, - queue: self.childTransactionQueue, - closure: closure).performAndWait() - } - // MARK: BaseDataTransaction @@ -213,32 +148,109 @@ public final class SynchronousDataTransaction: BaseDataTransaction { // MARK: Internal - internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue, closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) { - - self.closure = closure + internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue) { super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false) } - internal func performAndWait() -> SaveResult? { + internal func autoCommit(waitForMerge: Bool) -> (hasChanges: Bool, error: CoreStoreError?) { - self.transactionQueue.sync { - - self.closure(self) - - if !self.isCommitted && self.hasChanges { - - CoreStore.log( - .warning, - message: "The closure for the \(cs_typeName(self)) completed without being committed. All changes made within the transaction were discarded." - ) - } - } - return self.result + self.isCommitted = true + let result = self.context.saveSynchronously(waitForMerge: waitForMerge) + self.result = result + return result } - // MARK: Private + // MARK: Deprecated - private let closure: (_ transaction: SynchronousDataTransaction) -> Void + /** + Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once. + - Important: Unlike `SynchronousDataTransaction.commit()`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. + + - returns: a `SaveResult` containing the success or failure information + */ + @available(*, deprecated: 4.0.0, message: "Use the new auto-commit method DataStack.perform(synchronous:waitForAllObservers:)") + public func commitAndWait() -> SaveResult { + + CoreStore.assert( + self.transactionQueue.cs_isCurrentExecutionContext(), + "Attempted to commit a \(cs_typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to commit a \(cs_typeName(self)) more than once." + ) + switch self.autoCommit(waitForMerge: true) { + + case (let hasChanges, nil): return SaveResult(hasChanges: hasChanges) + case (_, let error?): return SaveResult(error) + } + } + + /** + Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once. + - Important: Unlike `SynchronousDataTransaction.commitAndWait()`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. + + - returns: a `SaveResult` containing the success or failure information + */ + @available(*, deprecated: 4.0.0, message: "Use the new auto-commit method DataStack.perform(synchronous:waitForAllObservers:)") + public func commit() -> SaveResult { + + CoreStore.assert( + self.transactionQueue.cs_isCurrentExecutionContext(), + "Attempted to commit a \(cs_typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to commit a \(cs_typeName(self)) more than once." + ) + switch self.autoCommit(waitForMerge: false) { + + case (let hasChanges, nil): return SaveResult(hasChanges: hasChanges) + case (_, let error?): return SaveResult(error) + } + } + + /** + Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ + @available(*, deprecated: 4.0.0, message: "Secondary tasks spawned from AsynchronousDataTransactions and SynchronousDataTransactions are no longer supported. ") + @discardableResult + public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { + + CoreStore.assert( + self.transactionQueue.cs_isCurrentExecutionContext(), + "Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to begin a child transaction from an already committed \(cs_typeName(self))." + ) + let childTransaction = SynchronousDataTransaction( + mainContext: self.context, + queue: self.childTransactionQueue + ) + childTransaction.transactionQueue.cs_sync { + + closure(childTransaction) + + if !childTransaction.isCommitted && childTransaction.hasChanges { + + CoreStore.log( + .warning, + message: "The closure for the \(cs_typeName(childTransaction)) completed without being committed. All changes made within the transaction were discarded." + ) + } + } + switch childTransaction.result { + + case nil: return nil + case (let hasChanges, nil)?: return SaveResult(hasChanges: hasChanges) + case (_, let error?)?: return SaveResult(error) + } + } } diff --git a/Sources/Transactions/TransactionResult.swift b/Sources/Transactions/TransactionResult.swift deleted file mode 100644 index 16f29cf..0000000 --- a/Sources/Transactions/TransactionResult.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// TransactionResult.swift -// CoreStore -// -// Copyright © 2017 John Rommel Estropia -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation - - -// MARK: - TransactionResult - -public enum TransactionResult { - - case success(T) - - case failure(CoreStoreError) - - public var boolValue: Bool { - - switch self { - - case .success: return true - case .failure: return false - } - } - - - // MARK: Internal - - internal init(userInfo: T) { - - self = .success(userInfo) - } - - internal init(error: CoreStoreError) { - - self = .failure(error) - } -} diff --git a/Sources/Transactions/UnsafeDataTransaction.swift b/Sources/Transactions/UnsafeDataTransaction.swift index 6e18f10..d5a6106 100644 --- a/Sources/Transactions/UnsafeDataTransaction.swift +++ b/Sources/Transactions/UnsafeDataTransaction.swift @@ -34,30 +34,33 @@ import CoreData */ public final class UnsafeDataTransaction: BaseDataTransaction { + // MARK: - + /** Saves the transaction changes asynchronously. For an `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread. - - parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block. + - parameter completion: the block executed after the save completes. Success or failure is reported by the optional `error` argument of the block. */ - public func commit(_ completion: @escaping (_ result: SaveResult) -> Void) { + public func commit(_ completion: @escaping (_ error: CoreStoreError?) -> Void) { - self.context.saveAsynchronouslyWithCompletion { (result) -> Void in + self.context.saveAsynchronouslyWithCompletion { (_, error) in - self.result = result - completion(result) + completion(error) + withExtendedLifetime(self, {}) } } /** Saves the transaction changes and waits for completion synchronously. For an `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread. - - returns: a `SaveResult` containing the success or failure information + - throws: a `CoreStoreError` value indicating the failure. */ - public func commitAndWait() -> SaveResult { + public func commitAndWait() throws { - let result = self.context.saveSynchronously(waitForMerge: true) - self.result = result - return result + if case (_, let error?) = self.context.saveSynchronously(waitForMerge: true) { + + throw error + } } /** @@ -142,13 +145,4 @@ public final class UnsafeDataTransaction: BaseDataTransaction { super.init(mainContext: mainContext, queue: queue, supportsUndo: supportsUndo, bypassesQueueing: true) } - - - // MARK: Obsolete - - @available(*, obsoleted: 3.0.0, message: "Transaction contexts are now exposed through the FetchableSource and QueryableSource protocols.", renamed: "internalContext()") - public var internalContext: NSManagedObjectContext { - - fatalError() - } }