diff --git a/CoreStoreTests/ImportTests.swift b/CoreStoreTests/ImportTests.swift index 3435b97..5b173e9 100644 --- a/CoreStoreTests/ImportTests.swift +++ b/CoreStoreTests/ImportTests.swift @@ -414,6 +414,65 @@ class ImportTests: BaseTestDataTestCase { } } + @objc + dynamic func test_ThatImportUniqueObjects_ImportsLastOfImportSourcesWithSameIDs() { + + 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: 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() + } + } + } + + @objc dynamic func test_ThatImportUniqueObject_CanThrowError() { diff --git a/Sources/Importing/BaseDataTransaction+Importing.swift b/Sources/Importing/BaseDataTransaction+Importing.swift index f142e9f..4acf448 100644 --- a/Sources/Importing/BaseDataTransaction+Importing.swift +++ b/Sources/Importing/BaseDataTransaction+Importing.swift @@ -186,6 +186,7 @@ public extension BaseDataTransaction { Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources. Objects are called with `ImportableUniqueObject` methods in the same order as in import sources array. The array returned from `importUniqueObjects(...)` correctly maps to the order of `sourceArray`. + If `sourceArray` contains multiple import sources with same ID, the last one will be imported. - parameter into: an `Into` clause specifying the entity type - parameter sourceArray: the array of objects to import values from @@ -217,7 +218,8 @@ public extension BaseDataTransaction { return nil } - + + // each subsequent import source with the same ID will replace the existing one importSourceByID[uniqueIDValue] = source return uniqueIDValue } @@ -230,32 +232,36 @@ public extension BaseDataTransaction { self.fetchAll(From(entityType), Where(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs))? .forEach { existingObjectsByID[$0.uniqueIDValue] = $0 } - var insertedObjectsByID = Dictionary() + var processedObjectIDs = Set() + var result = [T]() for objectID in sortedIDs { try autoreleasepool { - guard let source = importSourceByID[objectID] else { return } - + guard let source = importSourceByID[objectID], !processedObjectIDs.contains(objectID) else { return } if let object = existingObjectsByID[objectID] { guard entityType.shouldUpdate(from: source, in: self) else { return } try object.update(from: source, in: self) + + result.append(object) } else if entityType.shouldInsert(from: source, in: self) { let object = self.create(into) object.uniqueIDValue = objectID try object.didInsert(from: source, in: self) - - insertedObjectsByID[objectID] = object + + result.append(object) } + + processedObjectIDs.insert(objectID) } } - return sortedIDs.flatMap { existingObjectsByID[$0] ?? insertedObjectsByID[$0] } + return result } } }