mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-20 16:44:08 +01:00
fix duplication when using importUniqueObjects with non-unique import sources
This commit is contained in:
@@ -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<TestEntity1>(),
|
||||||
|
sourceArray: sourceArray
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(objects.count, 1)
|
||||||
|
XCTAssertEqual(transaction.fetchCount(From<TestEntity1>()), 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
|
@objc
|
||||||
dynamic func test_ThatImportUniqueObject_CanThrowError() {
|
dynamic func test_ThatImportUniqueObject_CanThrowError() {
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ public extension BaseDataTransaction {
|
|||||||
Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources.
|
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.
|
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`.
|
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 into: an `Into` clause specifying the entity type
|
||||||
- parameter sourceArray: the array of objects to import values from
|
- parameter sourceArray: the array of objects to import values from
|
||||||
@@ -217,7 +218,8 @@ public extension BaseDataTransaction {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// each subsequent import source with the same ID will replace the existing one
|
||||||
importSourceByID[uniqueIDValue] = source
|
importSourceByID[uniqueIDValue] = source
|
||||||
return uniqueIDValue
|
return uniqueIDValue
|
||||||
}
|
}
|
||||||
@@ -230,32 +232,36 @@ public extension BaseDataTransaction {
|
|||||||
self.fetchAll(From(entityType), Where(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs))?
|
self.fetchAll(From(entityType), Where(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs))?
|
||||||
.forEach { existingObjectsByID[$0.uniqueIDValue] = $0 }
|
.forEach { existingObjectsByID[$0.uniqueIDValue] = $0 }
|
||||||
|
|
||||||
var insertedObjectsByID = Dictionary<T.UniqueIDType, T>()
|
var processedObjectIDs = Set<T.UniqueIDType>()
|
||||||
|
var result = [T]()
|
||||||
|
|
||||||
for objectID in sortedIDs {
|
for objectID in sortedIDs {
|
||||||
|
|
||||||
try autoreleasepool {
|
try autoreleasepool {
|
||||||
|
|
||||||
guard let source = importSourceByID[objectID] else { return }
|
guard let source = importSourceByID[objectID], !processedObjectIDs.contains(objectID) else { return }
|
||||||
|
|
||||||
|
|
||||||
if let object = existingObjectsByID[objectID] {
|
if let object = existingObjectsByID[objectID] {
|
||||||
guard entityType.shouldUpdate(from: source, in: self) else { return }
|
guard entityType.shouldUpdate(from: source, in: self) else { return }
|
||||||
|
|
||||||
try object.update(from: source, in: self)
|
try object.update(from: source, in: self)
|
||||||
|
|
||||||
|
result.append(object)
|
||||||
}
|
}
|
||||||
else if entityType.shouldInsert(from: source, in: self) {
|
else if entityType.shouldInsert(from: source, in: self) {
|
||||||
let object = self.create(into)
|
let object = self.create(into)
|
||||||
object.uniqueIDValue = objectID
|
object.uniqueIDValue = objectID
|
||||||
try object.didInsert(from: source, in: self)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user