Skip to content

Commit 42ea7a3

Browse files
committed
Fix discovery of dependent entities
The resolver only returned the entities for the types it was asked for, which is incorrect, it also needs to return the dependencies for MOM setup.
1 parent c420371 commit 42ea7a3

File tree

4 files changed

+69
-46
lines changed

4 files changed

+69
-46
lines changed

Sources/ManagedModelMacros/ModelMacro/ModelMacro.swift

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,4 @@
44
//
55

66
public enum ModelMacro {
7-
/*
8-
@attached(member, ...)
9-
@attached(memberAttribute)
10-
@attached(extension, conformances: // the protocols we add automagically
11-
PersistentModel
12-
)
13-
14-
Note: @Model does not support `originalName`/`renamingIdentifier`?
15-
We could. Some for that versionHash thing.
16-
*/
177
}

Sources/ManagedModels/SchemaCompatibility/NSManagedObjectModel+Data.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public extension NSManagedObjectModel {
2323
version: Schema.Version = Version(1, 0, 0))
2424
{
2525
self.init()
26-
self.entities = SchemaBuilder.shared.resolve(types)
26+
self.entities = SchemaBuilder.shared.lookupAllEntities(for: types)
2727
}
2828

2929
@inlinable
@@ -80,7 +80,7 @@ internal extension NSManagedObjectModel {
8080
schemaCache: SchemaBuilder)
8181
{
8282
self.init()
83-
self.entities = schemaCache.resolve(types)
83+
self.entities = schemaCache.lookupAllEntities(for: types)
8484
}
8585

8686
/// Initializer for testing purposes.

Sources/ManagedModels/SchemaGeneration/SchemaBuilder.swift

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -72,31 +72,13 @@ public final class SchemaBuilder {
7272
* Thread Safety: This is a thread-safe operation. Do NOT modify the returned
7373
* Schema objects.
7474
*/
75-
func resolve(_ modelTypes: [ any PersistentModel.Type ])
75+
func lookupAllEntities(for modelTypes: [ any PersistentModel.Type ])
7676
-> [ NSEntityDescription ]
7777
{
78-
var entities = [ NSEntityDescription ]()
79-
var entitiesByName = [ String : NSEntityDescription ]()
78+
var entities = [ NSEntityDescription ]()
8079

8180
lock.lock()
82-
process(modelTypes)
83-
84-
for modelType in modelTypes {
85-
guard let entity = lookupEntity(modelType) else {
86-
fatalError(
87-
"""
88-
Could not construct `NSEntityDescription` for `PersistentModel` type
89-
90-
\(modelType)
91-
92-
"""
93-
)
94-
}
95-
// Return just the ones we need.
96-
entities.append(entity)
97-
precondition(entity.name != nil, "Entity w/o a name? \(entity)")
98-
if let name = entity.name { entitiesByName[name] = entity }
99-
}
81+
process(modelTypes, entities: &entities)
10082
lock.unlock()
10183

10284
return entities
@@ -115,10 +97,11 @@ public final class SchemaBuilder {
11597
public func _entity<M>(for modelType: M.Type) -> NSEntityDescription
11698
where M: PersistentModel
11799
{
100+
var entities = [ NSEntityDescription ]()
118101
let modelTypes = [ modelType ]
119102
lock.lock()
120103
let entity = lookupEntity(modelType) ?? {
121-
process(modelTypes)
104+
process(modelTypes, entities: &entities)
122105
guard let entity = lookupEntity(modelType) else {
123106
fatalError(
124107
"Could not construct Entity for PersistentModel type: \(modelType)")
@@ -132,29 +115,33 @@ public final class SchemaBuilder {
132115

133116
// MARK: - Things that run in the lock!
134117

135-
private func process(_ modelTypes: [ any PersistentModel.Type ]) {
118+
private func process(_ modelTypes: [ any PersistentModel.Type ],
119+
entities: inout [ NSEntityDescription ])
120+
{
136121
// Note: This is called recursively
137122
var allFrozen = false
138123

139124
// Create the basic entity and property data
140125
for modelType in modelTypes {
141126
guard !isFrozen(modelType) else { continue }
142127
allFrozen = false
143-
/* newEntity */ _ = processModel(modelType)
128+
if let newEntity = processModel(modelType) {
129+
entities.append(newEntity)
130+
}
144131
}
145132
if allFrozen { return } // all have been processed already
146133

147134
// TBD: The following does too much work, we might only need the
148135
// most of those on the "new models"
149136

150137
// This recurses into `process`, if necessary.
151-
discoverTargetTypes(entitiesByType.values)
138+
discoverTargetTypes(in: entities, allEntities: &entities)
152139

153140
// Collect destination entity names in relships based on the modelType!
154-
fillDestinationEntityNamesInRelationships(entitiesByType.values)
141+
fillDestinationEntityNamesInRelationships(entities)
155142

156143
// Lookup inverse relationships
157-
fillInverseRelationshipData(entitiesByType.values)
144+
fillInverseRelationshipData(entities)
158145

159146
frozenTypes.formUnion(entitiesByType.keys)
160147
}
@@ -164,21 +151,26 @@ public final class SchemaBuilder {
164151
* model type they point to is already tracked as an entity.
165152
* Recurses until all relationships have been processed.
166153
*/
167-
private func discoverTargetTypes<S>(_ entities: S)
168-
where S: Sequence, S.Element == NSEntityDescription
154+
private func discoverTargetTypes(in entities: [ NSEntityDescription ],
155+
allEntities: inout [ NSEntityDescription ])
169156
{
170157
var newEntities = [ NSEntityDescription ]()
171158
for entity in entities {
172159
for relationship in entity.relationships {
173-
guard let targetType = relationship.modelType else { continue }
174-
if let newEntity = processModel(targetType) {
175-
newEntities.append(newEntity)
160+
guard let targetType = relationship.modelType else {
161+
assertionFailure("Missing type for relationship \(relationship)")
162+
continue
176163
}
164+
// This returns nil if the model is already processed.
165+
guard let newEntity = processModel(targetType) else { continue }
166+
167+
allEntities.append(newEntity)
168+
newEntities.append(newEntity)
177169
}
178170
}
179171

180172
if !newEntities.isEmpty { // recurse if necessary
181-
discoverTargetTypes(newEntities)
173+
discoverTargetTypes(in: newEntities, allEntities: &allEntities)
182174
}
183175
}
184176

Tests/ManagedModelTests/SchemaGenerationTests.swift

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,48 @@ final class SchemaGenerationTests: XCTestCase {
7575
XCTAssertTrue (toAddresses.keypath == toPerson.inverseKeyPath)
7676
XCTAssertTrue (toAddresses.inverseKeyPath == toPerson.keypath)
7777
}
78-
78+
79+
func testAutomaticDependencies() throws {
80+
let cache = SchemaBuilder()
81+
let schema = NSManagedObjectModel(
82+
[ Fixtures.PersonAddressSchema.Person.self ],
83+
schemaCache: cache
84+
)
85+
86+
XCTAssertEqual(schema.entities.count, 2)
87+
XCTAssertEqual(schema.entitiesByName.count, 2)
88+
89+
let person = try XCTUnwrap(schema.entitiesByName["Person"])
90+
let address = try XCTUnwrap(schema.entitiesByName["Address"])
91+
92+
XCTAssertEqual(person.attributes.count, 2)
93+
XCTAssertEqual(person.relationships.count, 1)
94+
let firstname = try XCTUnwrap(person.attributesByName["firstname"])
95+
let lastname = try XCTUnwrap(person.attributesByName["lastname"])
96+
let toAddresses = try XCTUnwrap(person.relationshipsByName["addresses"])
97+
XCTAssertFalse (firstname.isTransient)
98+
XCTAssertFalse (lastname.isRelationship)
99+
XCTAssertTrue (toAddresses.isRelationship)
100+
XCTAssertFalse (toAddresses.isToOneRelationship)
101+
XCTAssertEqual (toAddresses.destination, "Address")
102+
XCTAssertNotNil(toAddresses.destinationEntity)
103+
104+
XCTAssertEqual(address.attributes.count, 2)
105+
XCTAssertEqual(address.relationships.count, 1)
106+
let toPerson = try XCTUnwrap(address.relationshipsByName["person"])
107+
XCTAssertTrue (toPerson.isRelationship)
108+
XCTAssertTrue (toPerson.isToOneRelationship)
109+
XCTAssertEqual(toPerson.destination, "Person")
110+
111+
XCTAssertTrue(toAddresses.destinationEntity === address)
112+
XCTAssertTrue(toPerson .destinationEntity === person)
113+
114+
XCTAssertEqual(toPerson .inverseName, "addresses")
115+
XCTAssertEqual(toAddresses.inverseName, "person")
116+
XCTAssertTrue (toAddresses.keypath == toPerson.inverseKeyPath)
117+
XCTAssertTrue (toAddresses.inverseKeyPath == toPerson.keypath)
118+
}
119+
79120
func testMissingInverse() throws {
80121
let cache = SchemaBuilder()
81122

0 commit comments

Comments
 (0)