@mikro-orm/core 7.0.11-dev.9 → 7.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EntityManager.d.ts +885 -584
- package/EntityManager.js +1927 -1900
- package/MikroORM.d.ts +103 -74
- package/MikroORM.js +177 -178
- package/README.md +1 -1
- package/cache/CacheAdapter.d.ts +36 -36
- package/cache/FileCacheAdapter.d.ts +30 -24
- package/cache/FileCacheAdapter.js +80 -78
- package/cache/GeneratedCacheAdapter.d.ts +18 -20
- package/cache/GeneratedCacheAdapter.js +30 -30
- package/cache/MemoryCacheAdapter.d.ts +18 -20
- package/cache/MemoryCacheAdapter.js +35 -36
- package/cache/NullCacheAdapter.d.ts +16 -16
- package/cache/NullCacheAdapter.js +24 -24
- package/connections/Connection.d.ts +95 -84
- package/connections/Connection.js +165 -168
- package/drivers/DatabaseDriver.d.ts +186 -80
- package/drivers/DatabaseDriver.js +450 -443
- package/drivers/IDatabaseDriver.d.ts +444 -301
- package/entity/BaseEntity.d.ts +120 -83
- package/entity/BaseEntity.js +43 -43
- package/entity/Collection.d.ts +215 -181
- package/entity/Collection.js +730 -724
- package/entity/EntityAssigner.d.ts +88 -77
- package/entity/EntityAssigner.js +231 -230
- package/entity/EntityFactory.d.ts +67 -55
- package/entity/EntityFactory.js +450 -411
- package/entity/EntityHelper.d.ts +35 -23
- package/entity/EntityHelper.js +302 -290
- package/entity/EntityIdentifier.d.ts +4 -4
- package/entity/EntityIdentifier.js +10 -10
- package/entity/EntityLoader.d.ts +98 -72
- package/entity/EntityLoader.js +815 -781
- package/entity/EntityRepository.d.ts +316 -201
- package/entity/EntityRepository.js +213 -213
- package/entity/PolymorphicRef.d.ts +5 -5
- package/entity/PolymorphicRef.js +10 -10
- package/entity/Reference.d.ts +127 -83
- package/entity/Reference.js +281 -277
- package/entity/WrappedEntity.d.ts +115 -72
- package/entity/WrappedEntity.js +168 -166
- package/entity/defineEntity.d.ts +1359 -654
- package/entity/defineEntity.js +527 -518
- package/entity/utils.d.ts +13 -3
- package/entity/utils.js +71 -73
- package/entity/validators.js +43 -43
- package/entity/wrap.js +8 -8
- package/enums.d.ts +258 -253
- package/enums.js +251 -252
- package/errors.d.ts +114 -72
- package/errors.js +350 -253
- package/events/EventManager.d.ts +26 -14
- package/events/EventManager.js +79 -77
- package/events/EventSubscriber.d.ts +29 -29
- package/events/TransactionEventBroadcaster.d.ts +15 -8
- package/events/TransactionEventBroadcaster.js +14 -14
- package/exceptions.d.ts +23 -40
- package/exceptions.js +35 -52
- package/hydration/Hydrator.d.ts +42 -17
- package/hydration/Hydrator.js +43 -43
- package/hydration/ObjectHydrator.d.ts +50 -17
- package/hydration/ObjectHydrator.js +483 -418
- package/index.d.ts +116 -2
- package/index.js +10 -1
- package/logging/DefaultLogger.d.ts +34 -32
- package/logging/DefaultLogger.js +86 -86
- package/logging/Logger.d.ts +41 -41
- package/logging/SimpleLogger.d.ts +13 -11
- package/logging/SimpleLogger.js +22 -22
- package/logging/colors.d.ts +6 -6
- package/logging/colors.js +11 -10
- package/logging/inspect.js +7 -7
- package/metadata/EntitySchema.d.ts +214 -130
- package/metadata/EntitySchema.js +411 -412
- package/metadata/MetadataDiscovery.d.ts +114 -114
- package/metadata/MetadataDiscovery.js +1965 -1884
- package/metadata/MetadataProvider.d.ts +29 -26
- package/metadata/MetadataProvider.js +95 -97
- package/metadata/MetadataStorage.d.ts +38 -32
- package/metadata/MetadataStorage.js +118 -118
- package/metadata/MetadataValidator.d.ts +39 -39
- package/metadata/MetadataValidator.js +385 -338
- package/metadata/discover-entities.d.ts +5 -2
- package/metadata/discover-entities.js +35 -37
- package/metadata/types.d.ts +615 -531
- package/naming-strategy/AbstractNamingStrategy.d.ts +54 -39
- package/naming-strategy/AbstractNamingStrategy.js +90 -85
- package/naming-strategy/EntityCaseNamingStrategy.d.ts +6 -6
- package/naming-strategy/EntityCaseNamingStrategy.js +22 -22
- package/naming-strategy/MongoNamingStrategy.d.ts +6 -6
- package/naming-strategy/MongoNamingStrategy.js +18 -18
- package/naming-strategy/NamingStrategy.d.ts +109 -99
- package/naming-strategy/UnderscoreNamingStrategy.d.ts +7 -7
- package/naming-strategy/UnderscoreNamingStrategy.js +21 -21
- package/not-supported.js +7 -4
- package/package.json +1 -1
- package/platforms/ExceptionConverter.d.ts +1 -1
- package/platforms/ExceptionConverter.js +4 -4
- package/platforms/Platform.d.ts +312 -303
- package/platforms/Platform.js +695 -675
- package/serialization/EntitySerializer.d.ts +49 -26
- package/serialization/EntitySerializer.js +224 -218
- package/serialization/EntityTransformer.d.ts +10 -6
- package/serialization/EntityTransformer.js +219 -217
- package/serialization/SerializationContext.d.ts +27 -23
- package/serialization/SerializationContext.js +105 -105
- package/types/ArrayType.d.ts +8 -8
- package/types/ArrayType.js +33 -33
- package/types/BigIntType.d.ts +17 -10
- package/types/BigIntType.js +37 -37
- package/types/BlobType.d.ts +3 -3
- package/types/BlobType.js +13 -13
- package/types/BooleanType.d.ts +4 -4
- package/types/BooleanType.js +12 -12
- package/types/CharacterType.d.ts +2 -2
- package/types/CharacterType.js +6 -6
- package/types/DateTimeType.d.ts +5 -5
- package/types/DateTimeType.js +15 -15
- package/types/DateType.d.ts +5 -5
- package/types/DateType.js +15 -15
- package/types/DecimalType.d.ts +7 -7
- package/types/DecimalType.js +26 -26
- package/types/DoubleType.d.ts +3 -3
- package/types/DoubleType.js +12 -12
- package/types/EnumArrayType.d.ts +5 -5
- package/types/EnumArrayType.js +24 -24
- package/types/EnumType.d.ts +3 -3
- package/types/EnumType.js +11 -11
- package/types/FloatType.d.ts +3 -3
- package/types/FloatType.js +9 -9
- package/types/IntegerType.d.ts +3 -3
- package/types/IntegerType.js +9 -9
- package/types/IntervalType.d.ts +4 -4
- package/types/IntervalType.js +12 -12
- package/types/JsonType.d.ts +8 -8
- package/types/JsonType.js +32 -32
- package/types/MediumIntType.d.ts +1 -1
- package/types/MediumIntType.js +3 -3
- package/types/SmallIntType.d.ts +3 -3
- package/types/SmallIntType.js +9 -9
- package/types/StringType.d.ts +4 -4
- package/types/StringType.js +12 -12
- package/types/TextType.d.ts +3 -3
- package/types/TextType.js +9 -9
- package/types/TimeType.d.ts +5 -5
- package/types/TimeType.js +17 -17
- package/types/TinyIntType.d.ts +3 -3
- package/types/TinyIntType.js +10 -10
- package/types/Type.d.ts +83 -79
- package/types/Type.js +82 -82
- package/types/Uint8ArrayType.d.ts +4 -4
- package/types/Uint8ArrayType.js +21 -21
- package/types/UnknownType.d.ts +4 -4
- package/types/UnknownType.js +12 -12
- package/types/UuidType.d.ts +5 -5
- package/types/UuidType.js +19 -19
- package/types/index.d.ts +75 -49
- package/types/index.js +52 -26
- package/typings.d.ts +1265 -744
- package/typings.js +244 -233
- package/unit-of-work/ChangeSet.d.ts +26 -26
- package/unit-of-work/ChangeSet.js +56 -56
- package/unit-of-work/ChangeSetComputer.d.ts +12 -12
- package/unit-of-work/ChangeSetComputer.js +187 -179
- package/unit-of-work/ChangeSetPersister.d.ts +69 -50
- package/unit-of-work/ChangeSetPersister.js +469 -446
- package/unit-of-work/CommitOrderCalculator.d.ts +40 -40
- package/unit-of-work/CommitOrderCalculator.js +89 -88
- package/unit-of-work/IdentityMap.d.ts +31 -31
- package/unit-of-work/IdentityMap.js +105 -105
- package/unit-of-work/UnitOfWork.d.ts +181 -141
- package/unit-of-work/UnitOfWork.js +1237 -1223
- package/utils/AbstractMigrator.d.ts +112 -91
- package/utils/AbstractMigrator.js +283 -275
- package/utils/AbstractSchemaGenerator.d.ts +43 -34
- package/utils/AbstractSchemaGenerator.js +121 -122
- package/utils/AsyncContext.d.ts +3 -3
- package/utils/AsyncContext.js +34 -35
- package/utils/Configuration.d.ts +852 -808
- package/utils/Configuration.js +359 -344
- package/utils/Cursor.d.ts +40 -22
- package/utils/Cursor.js +135 -127
- package/utils/DataloaderUtils.d.ts +58 -43
- package/utils/DataloaderUtils.js +203 -198
- package/utils/EntityComparator.d.ts +99 -82
- package/utils/EntityComparator.js +836 -737
- package/utils/NullHighlighter.d.ts +1 -1
- package/utils/NullHighlighter.js +3 -3
- package/utils/QueryHelper.d.ts +80 -51
- package/utils/QueryHelper.js +385 -364
- package/utils/RawQueryFragment.d.ts +50 -34
- package/utils/RawQueryFragment.js +122 -121
- package/utils/RequestContext.d.ts +32 -32
- package/utils/RequestContext.js +52 -53
- package/utils/TransactionContext.d.ts +16 -16
- package/utils/TransactionContext.js +27 -27
- package/utils/TransactionManager.d.ts +58 -58
- package/utils/TransactionManager.js +202 -200
- package/utils/Utils.d.ts +204 -145
- package/utils/Utils.js +826 -826
- package/utils/clone.js +105 -114
- package/utils/env-vars.js +90 -88
- package/utils/fs-utils.d.ts +15 -15
- package/utils/fs-utils.js +181 -182
- package/utils/upsert-utils.d.ts +20 -5
- package/utils/upsert-utils.js +114 -116
package/entity/EntityLoader.js
CHANGED
|
@@ -1,816 +1,850 @@
|
|
|
1
1
|
import { QueryHelper } from '../utils/QueryHelper.js';
|
|
2
2
|
import { Utils } from '../utils/Utils.js';
|
|
3
3
|
import { ValidationError } from '../errors.js';
|
|
4
|
-
import { LoadStrategy, PopulatePath, ReferenceKind
|
|
4
|
+
import { LoadStrategy, PopulatePath, ReferenceKind } from '../enums.js';
|
|
5
5
|
import { Reference } from './Reference.js';
|
|
6
6
|
import { helper } from './wrap.js';
|
|
7
7
|
import { expandDotPaths } from './utils.js';
|
|
8
8
|
import { Raw } from '../utils/RawQueryFragment.js';
|
|
9
9
|
/** Responsible for batch-loading entity relations using either select-in or joined loading strategies. */
|
|
10
10
|
export class EntityLoader {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
const meta = this.#metadata.find(entityName);
|
|
28
|
-
if (entities.some(e => !e.__helper)) {
|
|
29
|
-
const entity = entities.find(e => !Utils.isEntity(e));
|
|
30
|
-
throw ValidationError.notDiscoveredEntity(entity, meta, 'populate');
|
|
31
|
-
}
|
|
32
|
-
const references = entities.filter(e => !helper(e).isInitialized());
|
|
33
|
-
const visited = (options.visited ??= new Set());
|
|
34
|
-
options.where ??= {};
|
|
35
|
-
options.orderBy ??= {};
|
|
36
|
-
options.lookup ??= true;
|
|
37
|
-
options.validate ??= true;
|
|
38
|
-
options.refresh ??= false;
|
|
39
|
-
options.convertCustomTypes ??= true;
|
|
40
|
-
if (references.length > 0) {
|
|
41
|
-
await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
|
|
42
|
-
}
|
|
43
|
-
populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
|
|
44
|
-
const invalid = populate.find(({ field }) => !this.#em.canPopulate(entityName, field));
|
|
45
|
-
/* v8 ignore next */
|
|
46
|
-
if (options.validate && invalid) {
|
|
47
|
-
throw ValidationError.invalidPropertyName(entityName, invalid.field);
|
|
48
|
-
}
|
|
49
|
-
this.setSerializationContext(entities, populate, options);
|
|
50
|
-
const addedToVisited = [];
|
|
51
|
-
for (const entity of entities) {
|
|
52
|
-
if (visited.has(entity)) {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
addedToVisited.push(entity);
|
|
56
|
-
visited.add(entity);
|
|
57
|
-
}
|
|
58
|
-
for (const pop of populate) {
|
|
59
|
-
await this.populateField(entityName, entities, pop, options);
|
|
60
|
-
}
|
|
61
|
-
// Child-specific relations exist only on child metadata, so the parent-scoped populate loop above skips them (GH #7453).
|
|
62
|
-
if (Array.isArray(populate) &&
|
|
63
|
-
populate.some(p => p.all) &&
|
|
64
|
-
meta.inheritanceType === 'tpt' &&
|
|
65
|
-
meta.tptChildren?.length) {
|
|
66
|
-
const byType = new Map();
|
|
67
|
-
for (const entity of entities) {
|
|
68
|
-
const entityMeta = helper(entity).__meta;
|
|
69
|
-
if (entityMeta !== meta) {
|
|
70
|
-
const group = byType.get(entityMeta);
|
|
71
|
-
if (group) {
|
|
72
|
-
group.push(entity);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
byType.set(entityMeta, [entity]);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
for (const [childMeta, group] of byType) {
|
|
80
|
-
await this.populate(childMeta.class, group, true, {
|
|
81
|
-
...options,
|
|
82
|
-
lookup: false,
|
|
83
|
-
validate: false,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
for (const entity of addedToVisited) {
|
|
88
|
-
visited.delete(entity);
|
|
89
|
-
}
|
|
11
|
+
#metadata;
|
|
12
|
+
#driver;
|
|
13
|
+
#em;
|
|
14
|
+
constructor(em) {
|
|
15
|
+
this.#em = em;
|
|
16
|
+
this.#metadata = this.#em.getMetadata();
|
|
17
|
+
this.#driver = this.#em.getDriver();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Loads specified relations in batch.
|
|
21
|
+
* This will execute one query for each relation, that will populate it on all the specified entities.
|
|
22
|
+
*/
|
|
23
|
+
async populate(entityName, entities, populate, options) {
|
|
24
|
+
if (entities.length === 0 || Utils.isEmpty(populate)) {
|
|
25
|
+
return this.setSerializationContext(entities, populate, options);
|
|
90
26
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// oxfmt-ignore
|
|
96
|
-
return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
|
|
97
|
-
});
|
|
98
|
-
if (normalized.some(p => p.all)) {
|
|
99
|
-
normalized = this.lookupAllRelationships(entityName);
|
|
100
|
-
}
|
|
101
|
-
// convert nested `field` with dot syntax to PopulateOptions with `children` array
|
|
102
|
-
expandDotPaths(meta, normalized, true);
|
|
103
|
-
if (lookup && populate !== false) {
|
|
104
|
-
normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
|
|
105
|
-
// convert nested `field` with dot syntax produced by eager relations
|
|
106
|
-
expandDotPaths(meta, normalized, true);
|
|
107
|
-
}
|
|
108
|
-
// merge same fields
|
|
109
|
-
return this.mergeNestedPopulate(normalized);
|
|
110
|
-
}
|
|
111
|
-
setSerializationContext(entities, populate, options) {
|
|
112
|
-
for (const entity of entities) {
|
|
113
|
-
helper(entity).setSerializationContext({
|
|
114
|
-
populate,
|
|
115
|
-
fields: options.fields,
|
|
116
|
-
exclude: options.exclude,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
27
|
+
const meta = this.#metadata.find(entityName);
|
|
28
|
+
if (entities.some(e => !e.__helper)) {
|
|
29
|
+
const entity = entities.find(e => !Utils.isEntity(e));
|
|
30
|
+
throw ValidationError.notDiscoveredEntity(entity, meta, 'populate');
|
|
119
31
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!ret[item.field]) {
|
|
131
|
-
ret[item.field] = item;
|
|
132
|
-
return ret;
|
|
133
|
-
}
|
|
134
|
-
if (!ret[item.field].children && item.children) {
|
|
135
|
-
ret[item.field].children = item.children;
|
|
136
|
-
}
|
|
137
|
-
else if (ret[item.field].children && item.children) {
|
|
138
|
-
ret[item.field].children.push(...item.children);
|
|
139
|
-
}
|
|
140
|
-
return ret;
|
|
141
|
-
}, {});
|
|
142
|
-
return Object.values(tmp).map(item => {
|
|
143
|
-
if (item.children) {
|
|
144
|
-
item.children = this.mergeNestedPopulate(item.children);
|
|
145
|
-
}
|
|
146
|
-
return item;
|
|
147
|
-
});
|
|
32
|
+
const references = entities.filter(e => !helper(e).isInitialized());
|
|
33
|
+
const visited = (options.visited ??= new Set());
|
|
34
|
+
options.where ??= {};
|
|
35
|
+
options.orderBy ??= {};
|
|
36
|
+
options.lookup ??= true;
|
|
37
|
+
options.validate ??= true;
|
|
38
|
+
options.refresh ??= false;
|
|
39
|
+
options.convertCustomTypes ??= true;
|
|
40
|
+
if (references.length > 0) {
|
|
41
|
+
await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
|
|
148
42
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
orderBy: innerOrderBy,
|
|
192
|
-
}, !!(ref || prop.mapToPk));
|
|
193
|
-
const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
|
|
194
|
-
this.initializeCollections(filtered, prop, field, items, customOrder, partial);
|
|
195
|
-
return items;
|
|
196
|
-
}
|
|
197
|
-
async populateScalar(meta, filtered, options) {
|
|
198
|
-
const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
199
|
-
const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
|
|
200
|
-
const where = this.mergePrimaryCondition(ids, pk, options, meta, this.#metadata, this.#driver.getPlatform());
|
|
201
|
-
const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
|
|
202
|
-
await this.#em.find(meta.class, where, {
|
|
203
|
-
filters,
|
|
204
|
-
convertCustomTypes,
|
|
205
|
-
lockMode,
|
|
206
|
-
strategy,
|
|
207
|
-
populateWhere,
|
|
208
|
-
connectionType,
|
|
209
|
-
logging,
|
|
210
|
-
fields: fields,
|
|
211
|
-
populate: [],
|
|
43
|
+
populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
|
|
44
|
+
const invalid = populate.find(({ field }) => !this.#em.canPopulate(entityName, field));
|
|
45
|
+
/* v8 ignore next */
|
|
46
|
+
if (options.validate && invalid) {
|
|
47
|
+
throw ValidationError.invalidPropertyName(entityName, invalid.field);
|
|
48
|
+
}
|
|
49
|
+
this.setSerializationContext(entities, populate, options);
|
|
50
|
+
const addedToVisited = [];
|
|
51
|
+
for (const entity of entities) {
|
|
52
|
+
if (visited.has(entity)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
addedToVisited.push(entity);
|
|
56
|
+
visited.add(entity);
|
|
57
|
+
}
|
|
58
|
+
for (const pop of populate) {
|
|
59
|
+
await this.populateField(entityName, entities, pop, options);
|
|
60
|
+
}
|
|
61
|
+
// Child-specific relations exist only on child metadata, so the parent-scoped populate loop above skips them (GH #7453).
|
|
62
|
+
if (
|
|
63
|
+
Array.isArray(populate) &&
|
|
64
|
+
populate.some(p => p.all) &&
|
|
65
|
+
meta.inheritanceType === 'tpt' &&
|
|
66
|
+
meta.tptChildren?.length
|
|
67
|
+
) {
|
|
68
|
+
const byType = new Map();
|
|
69
|
+
for (const entity of entities) {
|
|
70
|
+
const entityMeta = helper(entity).__meta;
|
|
71
|
+
if (entityMeta !== meta) {
|
|
72
|
+
const group = byType.get(entityMeta);
|
|
73
|
+
if (group) {
|
|
74
|
+
group.push(entity);
|
|
75
|
+
} else {
|
|
76
|
+
byType.set(entityMeta, [entity]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const [childMeta, group] of byType) {
|
|
81
|
+
await this.populate(childMeta.class, group, true, {
|
|
82
|
+
...options,
|
|
83
|
+
lookup: false,
|
|
84
|
+
validate: false,
|
|
212
85
|
});
|
|
86
|
+
}
|
|
213
87
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// Separate entities: those with loaded refs vs those needing FK load
|
|
217
|
-
const toPopulate = [];
|
|
218
|
-
const needsFkLoad = [];
|
|
219
|
-
for (const entity of entities) {
|
|
220
|
-
const refValue = entity[prop.name];
|
|
221
|
-
if (refValue && helper(refValue).hasPrimaryKey()) {
|
|
222
|
-
if ((ref && !options.refresh) || // :ref hint - already have reference
|
|
223
|
-
(!ref && helper(refValue).__initialized && !options.refresh) // already loaded
|
|
224
|
-
) {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
toPopulate.push(entity);
|
|
228
|
-
}
|
|
229
|
-
else if (refValue == null && !helper(entity).__loadedProperties.has(prop.name)) {
|
|
230
|
-
// FK columns weren't loaded (partial loading) — need to re-fetch them.
|
|
231
|
-
// If the property IS in __loadedProperties, the FK was loaded and is genuinely null.
|
|
232
|
-
needsFkLoad.push(entity);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// Load FK columns using populateScalar pattern
|
|
236
|
-
if (needsFkLoad.length > 0) {
|
|
237
|
-
await this.populateScalar(ownerMeta, needsFkLoad, {
|
|
238
|
-
...options,
|
|
239
|
-
fields: [...ownerMeta.primaryKeys, prop.name],
|
|
240
|
-
});
|
|
241
|
-
// After loading FKs, add to toPopulate if not using :ref hint
|
|
242
|
-
if (!ref) {
|
|
243
|
-
for (const entity of needsFkLoad) {
|
|
244
|
-
const refValue = entity[prop.name];
|
|
245
|
-
if (refValue && helper(refValue).hasPrimaryKey()) {
|
|
246
|
-
toPopulate.push(entity);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
if (toPopulate.length === 0) {
|
|
252
|
-
return [];
|
|
253
|
-
}
|
|
254
|
-
// Group references by target class for batch loading
|
|
255
|
-
const groups = new Map();
|
|
256
|
-
for (const entity of toPopulate) {
|
|
257
|
-
const refValue = Reference.unwrapReference(entity[prop.name]);
|
|
258
|
-
const discriminator = QueryHelper.findDiscriminatorValue(prop.discriminatorMap, helper(refValue).__meta.class);
|
|
259
|
-
const group = groups.get(discriminator) ?? [];
|
|
260
|
-
group.push(refValue);
|
|
261
|
-
groups.set(discriminator, group);
|
|
262
|
-
}
|
|
263
|
-
// Load each group concurrently - identity map handles merging with existing references
|
|
264
|
-
const allItems = [];
|
|
265
|
-
await Promise.all([...groups].map(async ([discriminator, children]) => {
|
|
266
|
-
const targetMeta = this.#metadata.find(prop.discriminatorMap[discriminator]);
|
|
267
|
-
await this.populateScalar(targetMeta, children, options);
|
|
268
|
-
allItems.push(...children);
|
|
269
|
-
}));
|
|
270
|
-
return allItems;
|
|
271
|
-
}
|
|
272
|
-
initializeCollections(filtered, prop, field, children, customOrder, partial) {
|
|
273
|
-
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
274
|
-
this.initializeOneToMany(filtered, children, prop, field, partial);
|
|
275
|
-
}
|
|
276
|
-
if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.#driver.getPlatform().usesPivotTable()) {
|
|
277
|
-
this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
|
|
278
|
-
}
|
|
88
|
+
for (const entity of addedToVisited) {
|
|
89
|
+
visited.delete(entity);
|
|
279
90
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const key = helper(mapToPk ? this.#em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
|
|
291
|
-
map[key]?.push(child);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
for (const entity of filtered) {
|
|
295
|
-
const key = helper(entity).getSerializedPrimaryKey();
|
|
296
|
-
entity[field].hydrate(map[key], undefined, partial);
|
|
297
|
-
}
|
|
91
|
+
}
|
|
92
|
+
/** Normalizes populate hints into a structured array of PopulateOptions, expanding dot paths and eager relations. */
|
|
93
|
+
normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
|
|
94
|
+
const meta = this.#metadata.find(entityName);
|
|
95
|
+
let normalized = Utils.asArray(populate).map(field => {
|
|
96
|
+
// oxfmt-ignore
|
|
97
|
+
return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
|
|
98
|
+
});
|
|
99
|
+
if (normalized.some(p => p.all)) {
|
|
100
|
+
normalized = this.lookupAllRelationships(entityName);
|
|
298
101
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
// owning side of M:N without pivot table needs to be reordered
|
|
308
|
-
for (const entity of filtered) {
|
|
309
|
-
const order = !customOrder ? [...entity[prop.name].getItems(false)] : []; // copy order of references
|
|
310
|
-
const items = children.filter(child => entity[prop.name].contains(child, false));
|
|
311
|
-
if (!customOrder) {
|
|
312
|
-
items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
313
|
-
}
|
|
314
|
-
entity[field].hydrate(items, true, partial);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
102
|
+
// convert nested `field` with dot syntax to PopulateOptions with `children` array
|
|
103
|
+
expandDotPaths(meta, normalized, true);
|
|
104
|
+
if (lookup && populate !== false) {
|
|
105
|
+
normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
|
|
106
|
+
// convert nested `field` with dot syntax produced by eager relations
|
|
107
|
+
expandDotPaths(meta, normalized, true);
|
|
317
108
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
109
|
+
// merge same fields
|
|
110
|
+
return this.mergeNestedPopulate(normalized);
|
|
111
|
+
}
|
|
112
|
+
setSerializationContext(entities, populate, options) {
|
|
113
|
+
for (const entity of entities) {
|
|
114
|
+
helper(entity).setSerializationContext({
|
|
115
|
+
populate,
|
|
116
|
+
fields: options.fields,
|
|
117
|
+
exclude: options.exclude,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Merge multiple populates for the same entity with different children. Also skips `*` fields, those can come from
|
|
123
|
+
* partial loading hints (`fields`) that are used to infer the `populate` hint if missing.
|
|
124
|
+
*/
|
|
125
|
+
mergeNestedPopulate(populate) {
|
|
126
|
+
const tmp = populate.reduce((ret, item) => {
|
|
127
|
+
/* v8 ignore next */
|
|
128
|
+
if (item.field === PopulatePath.ALL) {
|
|
129
|
+
return ret;
|
|
130
|
+
}
|
|
131
|
+
if (!ret[item.field]) {
|
|
132
|
+
ret[item.field] = item;
|
|
133
|
+
return ret;
|
|
134
|
+
}
|
|
135
|
+
if (!ret[item.field].children && item.children) {
|
|
136
|
+
ret[item.field].children = item.children;
|
|
137
|
+
} else if (ret[item.field].children && item.children) {
|
|
138
|
+
ret[item.field].children.push(...item.children);
|
|
139
|
+
}
|
|
140
|
+
return ret;
|
|
141
|
+
}, {});
|
|
142
|
+
return Object.values(tmp).map(item => {
|
|
143
|
+
if (item.children) {
|
|
144
|
+
item.children = this.mergeNestedPopulate(item.children);
|
|
145
|
+
}
|
|
146
|
+
return item;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* preload everything in one call (this will update already existing references in IM)
|
|
151
|
+
*/
|
|
152
|
+
async populateMany(entityName, entities, populate, options) {
|
|
153
|
+
const [field, ref] = populate.field.split(':', 2);
|
|
154
|
+
const meta = this.#metadata.find(entityName);
|
|
155
|
+
const prop = meta.properties[field];
|
|
156
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !this.#driver.getPlatform().usesPivotTable()) {
|
|
157
|
+
const filtered = entities.filter(e => !e[prop.name]?.isInitialized());
|
|
158
|
+
if (filtered.length > 0) {
|
|
159
|
+
await this.populateScalar(meta, filtered, { ...options, fields: [prop.name] });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (prop.kind === ReferenceKind.SCALAR && prop.lazy) {
|
|
163
|
+
const filtered = entities.filter(
|
|
164
|
+
e => options.refresh || (prop.ref ? !e[prop.name]?.isInitialized() : e[prop.name] === undefined),
|
|
165
|
+
);
|
|
166
|
+
if (options.ignoreLazyScalarProperties || filtered.length === 0) {
|
|
167
|
+
return entities;
|
|
168
|
+
}
|
|
169
|
+
await this.populateScalar(meta, filtered, { ...options, fields: [prop.name] });
|
|
170
|
+
return entities;
|
|
171
|
+
}
|
|
172
|
+
if (prop.kind === ReferenceKind.EMBEDDED) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const filtered = this.filterCollections(entities, field, options, ref);
|
|
176
|
+
const innerOrderBy = Utils.asArray(options.orderBy)
|
|
177
|
+
.filter(
|
|
178
|
+
orderBy =>
|
|
179
|
+
(Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils.isObject(orderBy[prop.name]),
|
|
180
|
+
)
|
|
181
|
+
.flatMap(orderBy => orderBy[prop.name]);
|
|
182
|
+
const where = await this.extractChildCondition(options, prop);
|
|
183
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && this.#driver.getPlatform().usesPivotTable()) {
|
|
184
|
+
const pivotOrderBy = QueryHelper.mergeOrderBy(innerOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
|
|
185
|
+
const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
|
|
186
|
+
return Utils.flatten(res);
|
|
187
|
+
}
|
|
188
|
+
if (prop.polymorphic && prop.polymorphTargets) {
|
|
189
|
+
return this.populatePolymorphic(entities, prop, options, !!ref);
|
|
190
|
+
}
|
|
191
|
+
const { items, partial } = await this.findChildren(
|
|
192
|
+
options.filtered ?? entities,
|
|
193
|
+
prop,
|
|
194
|
+
populate,
|
|
195
|
+
{
|
|
196
|
+
...options,
|
|
197
|
+
where,
|
|
198
|
+
orderBy: innerOrderBy,
|
|
199
|
+
},
|
|
200
|
+
!!(ref || prop.mapToPk),
|
|
201
|
+
);
|
|
202
|
+
const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
|
|
203
|
+
this.initializeCollections(filtered, prop, field, items, customOrder, partial);
|
|
204
|
+
return items;
|
|
205
|
+
}
|
|
206
|
+
async populateScalar(meta, filtered, options) {
|
|
207
|
+
const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
208
|
+
const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
|
|
209
|
+
const where = this.mergePrimaryCondition(ids, pk, options, meta, this.#metadata, this.#driver.getPlatform());
|
|
210
|
+
const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
|
|
211
|
+
await this.#em.find(meta.class, where, {
|
|
212
|
+
filters,
|
|
213
|
+
convertCustomTypes,
|
|
214
|
+
lockMode,
|
|
215
|
+
strategy,
|
|
216
|
+
populateWhere,
|
|
217
|
+
connectionType,
|
|
218
|
+
logging,
|
|
219
|
+
fields: fields,
|
|
220
|
+
populate: [],
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async populatePolymorphic(entities, prop, options, ref) {
|
|
224
|
+
const ownerMeta = this.#metadata.get(entities[0].constructor);
|
|
225
|
+
// Separate entities: those with loaded refs vs those needing FK load
|
|
226
|
+
const toPopulate = [];
|
|
227
|
+
const needsFkLoad = [];
|
|
228
|
+
for (const entity of entities) {
|
|
229
|
+
const refValue = entity[prop.name];
|
|
230
|
+
if (refValue && helper(refValue).hasPrimaryKey()) {
|
|
231
|
+
if (
|
|
232
|
+
(ref && !options.refresh) || // :ref hint - already have reference
|
|
233
|
+
(!ref && helper(refValue).__initialized && !options.refresh) // already loaded
|
|
234
|
+
) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
toPopulate.push(entity);
|
|
238
|
+
} else if (refValue == null && !helper(entity).__loadedProperties.has(prop.name)) {
|
|
239
|
+
// FK columns weren't loaded (partial loading) — need to re-fetch them.
|
|
240
|
+
// If the property IS in __loadedProperties, the FK was loaded and is genuinely null.
|
|
241
|
+
needsFkLoad.push(entity);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Load FK columns using populateScalar pattern
|
|
245
|
+
if (needsFkLoad.length > 0) {
|
|
246
|
+
await this.populateScalar(ownerMeta, needsFkLoad, {
|
|
247
|
+
...options,
|
|
248
|
+
fields: [...ownerMeta.primaryKeys, prop.name],
|
|
249
|
+
});
|
|
250
|
+
// After loading FKs, add to toPopulate if not using :ref hint
|
|
251
|
+
if (!ref) {
|
|
252
|
+
for (const entity of needsFkLoad) {
|
|
253
|
+
const refValue = entity[prop.name];
|
|
254
|
+
if (refValue && helper(refValue).hasPrimaryKey()) {
|
|
255
|
+
toPopulate.push(entity);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (toPopulate.length === 0) {
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
// Group references by target class for batch loading
|
|
264
|
+
const groups = new Map();
|
|
265
|
+
for (const entity of toPopulate) {
|
|
266
|
+
const refValue = Reference.unwrapReference(entity[prop.name]);
|
|
267
|
+
const discriminator = QueryHelper.findDiscriminatorValue(prop.discriminatorMap, helper(refValue).__meta.class);
|
|
268
|
+
const group = groups.get(discriminator) ?? [];
|
|
269
|
+
group.push(refValue);
|
|
270
|
+
groups.set(discriminator, group);
|
|
271
|
+
}
|
|
272
|
+
// Load each group concurrently - identity map handles merging with existing references
|
|
273
|
+
const allItems = [];
|
|
274
|
+
await Promise.all(
|
|
275
|
+
[...groups].map(async ([discriminator, children]) => {
|
|
276
|
+
const targetMeta = this.#metadata.find(prop.discriminatorMap[discriminator]);
|
|
277
|
+
await this.populateScalar(targetMeta, children, options);
|
|
278
|
+
allItems.push(...children);
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
281
|
+
return allItems;
|
|
282
|
+
}
|
|
283
|
+
initializeCollections(filtered, prop, field, children, customOrder, partial) {
|
|
284
|
+
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
285
|
+
this.initializeOneToMany(filtered, children, prop, field, partial);
|
|
286
|
+
}
|
|
287
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.#driver.getPlatform().usesPivotTable()) {
|
|
288
|
+
this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
initializeOneToMany(filtered, children, prop, field, partial) {
|
|
292
|
+
const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
|
|
293
|
+
const map = {};
|
|
294
|
+
for (const entity of filtered) {
|
|
295
|
+
const key = helper(entity).getSerializedPrimaryKey();
|
|
296
|
+
map[key] = [];
|
|
297
|
+
}
|
|
298
|
+
for (const child of children) {
|
|
299
|
+
const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
|
|
300
|
+
if (pk) {
|
|
301
|
+
const key = helper(mapToPk ? this.#em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
|
|
302
|
+
map[key]?.push(child);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
for (const entity of filtered) {
|
|
306
|
+
const key = helper(entity).getSerializedPrimaryKey();
|
|
307
|
+
entity[field].hydrate(map[key], undefined, partial);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
|
|
311
|
+
if (prop.mappedBy) {
|
|
312
|
+
for (const entity of filtered) {
|
|
313
|
+
const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
|
|
314
|
+
entity[field].hydrate(items, true, partial);
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
// owning side of M:N without pivot table needs to be reordered
|
|
318
|
+
for (const entity of filtered) {
|
|
319
|
+
const order = !customOrder ? [...entity[prop.name].getItems(false)] : []; // copy order of references
|
|
320
|
+
const items = children.filter(child => entity[prop.name].contains(child, false));
|
|
321
|
+
if (!customOrder) {
|
|
322
|
+
items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
323
|
+
}
|
|
324
|
+
entity[field].hydrate(items, true, partial);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async findChildren(entities, prop, populate, options, ref) {
|
|
329
|
+
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
|
|
330
|
+
const meta = prop.targetMeta;
|
|
331
|
+
// When targetKey is set, use it for FK lookup instead of the PK
|
|
332
|
+
let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
333
|
+
let schema = options.schema;
|
|
334
|
+
const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
|
|
335
|
+
let polymorphicOwnerProp;
|
|
336
|
+
if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
|
|
337
|
+
const ownerProp = meta.properties[prop.mappedBy];
|
|
338
|
+
if (ownerProp.polymorphic && ownerProp.fieldNames.length >= 2) {
|
|
339
|
+
const idColumns = ownerProp.fieldNames.slice(1);
|
|
340
|
+
fk = idColumns.length === 1 ? idColumns[0] : idColumns;
|
|
341
|
+
polymorphicOwnerProp = ownerProp;
|
|
342
|
+
} else {
|
|
343
|
+
fk = ownerProp.name;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
|
|
347
|
+
children.length = 0;
|
|
348
|
+
fk = meta.properties[prop.mappedBy].name;
|
|
349
|
+
children.push(...this.filterByReferences(entities, prop.name, options.refresh));
|
|
350
|
+
}
|
|
351
|
+
if (children.length === 0) {
|
|
352
|
+
return { items: [], partial };
|
|
353
|
+
}
|
|
354
|
+
if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
|
355
|
+
schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
|
|
356
|
+
}
|
|
357
|
+
const ids = Utils.unique(children.map(e => (prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey())));
|
|
358
|
+
let where;
|
|
359
|
+
if (polymorphicOwnerProp && Array.isArray(fk)) {
|
|
360
|
+
const conditions = ids.map(id => {
|
|
361
|
+
const pkValues = Object.values(id);
|
|
362
|
+
return Object.fromEntries(fk.map((col, idx) => [col, pkValues[idx]]));
|
|
363
|
+
});
|
|
364
|
+
where = conditions.length === 1 ? conditions[0] : { $or: conditions };
|
|
365
|
+
} else {
|
|
366
|
+
where = this.mergePrimaryCondition(ids, fk, options, meta, this.#metadata, this.#driver.getPlatform());
|
|
367
|
+
}
|
|
368
|
+
if (polymorphicOwnerProp) {
|
|
369
|
+
const parentMeta = this.#metadata.find(entities[0].constructor);
|
|
370
|
+
const discriminatorValue =
|
|
371
|
+
QueryHelper.findDiscriminatorValue(polymorphicOwnerProp.discriminatorMap, parentMeta.class) ??
|
|
372
|
+
parentMeta.tableName;
|
|
373
|
+
const discriminatorColumn = polymorphicOwnerProp.fieldNames[0];
|
|
374
|
+
where = { $and: [where, { [discriminatorColumn]: discriminatorValue }] };
|
|
375
|
+
}
|
|
376
|
+
const fields = this.buildFields(options.fields, prop, ref);
|
|
377
|
+
/* eslint-disable prefer-const */
|
|
378
|
+
let {
|
|
379
|
+
refresh,
|
|
380
|
+
filters,
|
|
381
|
+
convertCustomTypes,
|
|
382
|
+
lockMode,
|
|
383
|
+
strategy,
|
|
384
|
+
populateWhere = 'infer',
|
|
385
|
+
connectionType,
|
|
386
|
+
logging,
|
|
387
|
+
} = options;
|
|
388
|
+
/* eslint-enable prefer-const */
|
|
389
|
+
if (typeof populateWhere === 'object') {
|
|
390
|
+
populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
|
|
391
|
+
}
|
|
392
|
+
if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
|
|
393
|
+
where = { $and: [where, prop.where] };
|
|
394
|
+
}
|
|
395
|
+
const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
|
|
396
|
+
const items = await this.#em.find(meta.class, where, {
|
|
397
|
+
filters,
|
|
398
|
+
convertCustomTypes,
|
|
399
|
+
lockMode,
|
|
400
|
+
populateWhere,
|
|
401
|
+
logging,
|
|
402
|
+
orderBy,
|
|
403
|
+
populate: populate.children ?? populate.all ?? [],
|
|
404
|
+
exclude: Array.isArray(options.exclude)
|
|
405
|
+
? Utils.extractChildElements(options.exclude, prop.name)
|
|
406
|
+
: options.exclude,
|
|
407
|
+
strategy,
|
|
408
|
+
fields,
|
|
409
|
+
schema,
|
|
410
|
+
connectionType,
|
|
411
|
+
// @ts-ignore not a public option, will be propagated to the populate call
|
|
412
|
+
refresh: refresh && !children.every(item => options.visited.has(item)),
|
|
413
|
+
// @ts-ignore not a public option, will be propagated to the populate call
|
|
414
|
+
visited: options.visited,
|
|
415
|
+
});
|
|
416
|
+
// For targetKey relations, wire up loaded entities to parent references
|
|
417
|
+
// This is needed because the references were created under alternate key,
|
|
418
|
+
// but loaded entities are stored under PK, so they don't automatically merge
|
|
419
|
+
if (prop.targetKey && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
|
420
|
+
const itemsByKey = new Map();
|
|
421
|
+
for (const item of items) {
|
|
422
|
+
itemsByKey.set('' + item[prop.targetKey], item);
|
|
423
|
+
}
|
|
424
|
+
for (const entity of entities) {
|
|
425
|
+
const ref = entity[prop.name];
|
|
426
|
+
/* v8 ignore next */
|
|
427
|
+
if (!ref) {
|
|
428
|
+
continue;
|
|
514
429
|
}
|
|
515
|
-
const fields = this.buildFields(options.fields, prop);
|
|
516
|
-
const innerOrderBy = Utils.asArray(options.orderBy)
|
|
517
|
-
.filter(orderBy => Utils.isObject(orderBy[prop.name]))
|
|
518
|
-
.map(orderBy => orderBy[prop.name]);
|
|
519
|
-
const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
|
|
520
430
|
// oxfmt-ignore
|
|
521
|
-
const
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
}
|
|
526
|
-
const unique = Utils.unique(children);
|
|
527
|
-
const filtered = unique.filter(e => !visited.has(e));
|
|
528
|
-
for (const entity of entities) {
|
|
529
|
-
visited.add(entity);
|
|
530
|
-
}
|
|
531
|
-
if (!prop.targetMeta) {
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
const populateChildren = async (targetMeta, items) => {
|
|
535
|
-
await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
|
|
536
|
-
where: (await this.extractChildCondition(options, prop, false)),
|
|
537
|
-
orderBy: innerOrderBy,
|
|
538
|
-
fields,
|
|
539
|
-
exclude,
|
|
540
|
-
validate: false,
|
|
541
|
-
lookup: false,
|
|
542
|
-
filters,
|
|
543
|
-
ignoreLazyScalarProperties,
|
|
544
|
-
populateWhere,
|
|
545
|
-
connectionType,
|
|
546
|
-
logging,
|
|
547
|
-
schema,
|
|
548
|
-
// @ts-ignore not a public option, will be propagated to the populate call
|
|
549
|
-
refresh: refresh && !filtered.every(item => options.visited.has(item)),
|
|
550
|
-
// @ts-ignore not a public option, will be propagated to the populate call
|
|
551
|
-
visited: options.visited,
|
|
552
|
-
// @ts-ignore not a public option
|
|
553
|
-
filtered,
|
|
554
|
-
});
|
|
555
|
-
};
|
|
556
|
-
if (prop.polymorphic && prop.polymorphTargets) {
|
|
557
|
-
await Promise.all(prop.polymorphTargets.map(async (targetMeta) => {
|
|
558
|
-
const targetChildren = unique.filter(child => helper(child).__meta.className === targetMeta.className);
|
|
559
|
-
if (targetChildren.length > 0) {
|
|
560
|
-
await populateChildren(targetMeta, targetChildren);
|
|
561
|
-
}
|
|
562
|
-
}));
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
await populateChildren(prop.targetMeta, unique);
|
|
431
|
+
const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
|
|
432
|
+
const loadedItem = itemsByKey.get(keyValue);
|
|
433
|
+
if (loadedItem) {
|
|
434
|
+
entity[prop.name] = Reference.isReference(ref) ? Reference.create(loadedItem) : loadedItem;
|
|
566
435
|
}
|
|
436
|
+
}
|
|
567
437
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
438
|
+
if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
|
|
439
|
+
const nullVal = this.#em.config.get('forceUndefined') ? undefined : null;
|
|
440
|
+
const itemsMap = new Set();
|
|
441
|
+
const childrenMap = new Set();
|
|
442
|
+
// Use targetKey value if set, otherwise use serialized PK
|
|
443
|
+
const getKey = e => (prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey());
|
|
444
|
+
for (const item of items) {
|
|
445
|
+
/* v8 ignore next */
|
|
446
|
+
itemsMap.add(getKey(item));
|
|
447
|
+
}
|
|
448
|
+
for (const child of children) {
|
|
449
|
+
childrenMap.add(getKey(child));
|
|
450
|
+
}
|
|
451
|
+
const isInverseOneToOne = prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
452
|
+
for (const entity of entities) {
|
|
453
|
+
const ref = entity[prop.name];
|
|
454
|
+
if (ref == null) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const refKey = getKey(ref);
|
|
458
|
+
// For 1:1 inverse, `children` contains parent entities, so `childrenMap`
|
|
459
|
+
// has parent PKs — match against the entity's own PK, not the referenced entity's PK.
|
|
460
|
+
const childKey = isInverseOneToOne ? getKey(entity) : refKey;
|
|
461
|
+
if (childrenMap.has(childKey) && !itemsMap.has(refKey)) {
|
|
462
|
+
entity[prop.name] = nullVal;
|
|
463
|
+
helper(entity).__originalEntityData[prop.name] = null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
for (const item of items) {
|
|
468
|
+
if (ref && !helper(item).__onLoadFired) {
|
|
469
|
+
helper(item).__initialized = false;
|
|
470
|
+
this.#em.getUnitOfWork().unmarkAsLoaded(item);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return { items, partial };
|
|
474
|
+
}
|
|
475
|
+
mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
|
|
476
|
+
const cond1 = QueryHelper.processWhere({
|
|
477
|
+
where: { [pk]: { $in: ids } },
|
|
478
|
+
entityName: meta.class,
|
|
479
|
+
metadata,
|
|
480
|
+
platform,
|
|
481
|
+
convertCustomTypes: !options.convertCustomTypes,
|
|
482
|
+
});
|
|
483
|
+
const where = { ...options.where };
|
|
484
|
+
Utils.dropUndefinedProperties(where);
|
|
485
|
+
return where[pk] ? { $and: [cond1, where] } : { ...cond1, ...where };
|
|
486
|
+
}
|
|
487
|
+
async populateField(entityName, entities, populate, options) {
|
|
488
|
+
const field = populate.field.split(':')[0];
|
|
489
|
+
const prop = this.#metadata.find(entityName).properties[field];
|
|
490
|
+
if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
|
|
494
|
+
const populated = await this.populateMany(entityName, entities, populate, options);
|
|
495
|
+
if (!populate.children && !populate.all) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const children = [];
|
|
499
|
+
for (const entity of entities) {
|
|
500
|
+
const ref = entity[field];
|
|
501
|
+
if (Utils.isEntity(ref)) {
|
|
502
|
+
children.push(ref);
|
|
503
|
+
} else if (Reference.isReference(ref)) {
|
|
504
|
+
children.push(ref.unwrap());
|
|
505
|
+
} else if (Utils.isCollection(ref)) {
|
|
506
|
+
children.push(...ref.getItems());
|
|
507
|
+
} else if (ref && prop.kind === ReferenceKind.EMBEDDED) {
|
|
508
|
+
children.push(...Utils.asArray(ref));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (populated.length === 0 && !populate.children) {
|
|
512
|
+
// Populate child-specific relations for TPT entities already in the identity map (GH #7529).
|
|
513
|
+
// Pass a sentinel hint so the existing TPT child handling in `populate()` does the real work.
|
|
514
|
+
if (populate.all && prop.targetMeta?.inheritanceType === 'tpt') {
|
|
515
|
+
const visited = options.visited;
|
|
516
|
+
const pending = Utils.unique(children).filter(c => helper(c).__meta !== prop.targetMeta && !visited.has(c));
|
|
517
|
+
if (pending.length > 0) {
|
|
518
|
+
const hint = [{ field: prop.targetMeta.primaryKeys[0], strategy: LoadStrategy.SELECT_IN, all: true }];
|
|
519
|
+
await this.populate(prop.targetMeta.class, pending, hint, {
|
|
520
|
+
...options,
|
|
521
|
+
lookup: false,
|
|
522
|
+
validate: false,
|
|
523
|
+
});
|
|
604
524
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const fields = this.buildFields(options.fields, prop);
|
|
529
|
+
const innerOrderBy = Utils.asArray(options.orderBy)
|
|
530
|
+
.filter(orderBy => Utils.isObject(orderBy[prop.name]))
|
|
531
|
+
.map(orderBy => orderBy[prop.name]);
|
|
532
|
+
const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
|
|
533
|
+
// oxfmt-ignore
|
|
534
|
+
const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
|
|
535
|
+
const visited = options.visited;
|
|
536
|
+
for (const entity of entities) {
|
|
537
|
+
visited.delete(entity);
|
|
538
|
+
}
|
|
539
|
+
const unique = Utils.unique(children);
|
|
540
|
+
const filtered = unique.filter(e => !visited.has(e));
|
|
541
|
+
for (const entity of entities) {
|
|
542
|
+
visited.add(entity);
|
|
543
|
+
}
|
|
544
|
+
if (!prop.targetMeta) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const populateChildren = async (targetMeta, items) => {
|
|
548
|
+
await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
|
|
549
|
+
where: await this.extractChildCondition(options, prop, false),
|
|
550
|
+
orderBy: innerOrderBy,
|
|
551
|
+
fields,
|
|
552
|
+
exclude,
|
|
553
|
+
validate: false,
|
|
554
|
+
lookup: false,
|
|
555
|
+
filters,
|
|
556
|
+
ignoreLazyScalarProperties,
|
|
557
|
+
populateWhere,
|
|
558
|
+
connectionType,
|
|
559
|
+
logging,
|
|
560
|
+
schema,
|
|
561
|
+
// @ts-ignore not a public option, will be propagated to the populate call
|
|
562
|
+
refresh: refresh && !filtered.every(item => options.visited.has(item)),
|
|
563
|
+
// @ts-ignore not a public option, will be propagated to the populate call
|
|
564
|
+
visited: options.visited,
|
|
565
|
+
// @ts-ignore not a public option
|
|
566
|
+
filtered,
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
if (prop.polymorphic && prop.polymorphTargets) {
|
|
570
|
+
await Promise.all(
|
|
571
|
+
prop.polymorphTargets.map(async targetMeta => {
|
|
572
|
+
const targetChildren = unique.filter(child => {
|
|
573
|
+
const childMeta = helper(child).__meta;
|
|
574
|
+
// Use root.className for TPT entities: a Dog (root: Animal) should match
|
|
575
|
+
// targetMeta Animal. For non-TPT entities, root === self.
|
|
576
|
+
return childMeta.root.className === targetMeta.root.className;
|
|
577
|
+
});
|
|
578
|
+
if (targetChildren.length > 0) {
|
|
579
|
+
await populateChildren(targetMeta, targetChildren);
|
|
580
|
+
}
|
|
581
|
+
}),
|
|
582
|
+
);
|
|
583
|
+
} else {
|
|
584
|
+
await populateChildren(prop.targetMeta, unique);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/** @internal */
|
|
588
|
+
async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
|
|
589
|
+
const ids = filtered.map(e => e.__helper.__primaryKeys);
|
|
590
|
+
const refresh = options.refresh;
|
|
591
|
+
let where = await this.extractChildCondition(options, prop, true);
|
|
592
|
+
const fields = this.buildFields(options.fields, prop);
|
|
593
|
+
// oxfmt-ignore
|
|
594
|
+
const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
|
|
595
|
+
const populateFilter = options.populateFilter?.[prop.name];
|
|
596
|
+
const options2 = { ...options, fields, exclude, populateFilter };
|
|
597
|
+
['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
|
|
598
|
+
options2.populate = populate?.children ?? [];
|
|
599
|
+
if (!Utils.isEmpty(prop.where)) {
|
|
600
|
+
where = { $and: [where, prop.where] };
|
|
601
|
+
}
|
|
602
|
+
const map = await this.#driver.loadFromPivotTable(
|
|
603
|
+
prop,
|
|
604
|
+
ids,
|
|
605
|
+
where,
|
|
606
|
+
orderBy,
|
|
607
|
+
this.#em.getTransactionContext(),
|
|
608
|
+
options2,
|
|
609
|
+
pivotJoin,
|
|
610
|
+
);
|
|
611
|
+
const children = [];
|
|
612
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
613
|
+
const entity = filtered[i];
|
|
614
|
+
const items = map[Utils.getPrimaryKeyHash(ids[i])].map(item => {
|
|
615
|
+
if (pivotJoin) {
|
|
616
|
+
return this.#em.getReference(prop.targetMeta.class, item, {
|
|
617
|
+
convertCustomTypes: true,
|
|
618
|
+
schema: options.schema ?? this.#em.config.get('schema'),
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
const entity = this.#em.getEntityFactory().create(prop.targetMeta.class, item, {
|
|
622
|
+
refresh,
|
|
623
|
+
merge: true,
|
|
624
|
+
convertCustomTypes: true,
|
|
625
|
+
schema: options.schema ?? this.#em.config.get('schema'),
|
|
628
626
|
});
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
subCond[pk][op] = subCond[op];
|
|
634
|
-
delete subCond[op];
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
if (filters) {
|
|
638
|
-
return this.#em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
|
|
639
|
-
}
|
|
640
|
-
return subCond;
|
|
627
|
+
return this.#em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
|
|
628
|
+
});
|
|
629
|
+
entity[prop.name].hydrate(items, true);
|
|
630
|
+
children.push(items);
|
|
641
631
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
632
|
+
return children;
|
|
633
|
+
}
|
|
634
|
+
async extractChildCondition(options, prop, filters = false) {
|
|
635
|
+
const where = options.where;
|
|
636
|
+
const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
|
|
637
|
+
const meta2 = prop.targetMeta;
|
|
638
|
+
const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
|
|
639
|
+
['$and', '$or'].forEach(op => {
|
|
640
|
+
if (where[op]) {
|
|
641
|
+
const child = where[op]
|
|
642
|
+
.map(cond => cond[prop.name])
|
|
643
|
+
.filter(
|
|
644
|
+
sub =>
|
|
645
|
+
sub != null &&
|
|
646
|
+
!(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))),
|
|
647
|
+
)
|
|
648
|
+
.map(cond => {
|
|
649
|
+
if (Utils.isPrimaryKey(cond)) {
|
|
650
|
+
return { [pk]: cond };
|
|
651
|
+
}
|
|
652
|
+
return cond;
|
|
653
|
+
});
|
|
654
|
+
if (child.length > 0) {
|
|
655
|
+
subCond[op] = child;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
const operators = Object.keys(subCond).filter(key => Utils.isOperator(key, false));
|
|
660
|
+
if (operators.length > 0) {
|
|
661
|
+
operators.forEach(op => {
|
|
662
|
+
subCond[pk] ??= {};
|
|
663
|
+
subCond[pk][op] = subCond[op];
|
|
664
|
+
delete subCond[op];
|
|
665
|
+
});
|
|
676
666
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
680
|
-
return filtered.map(e => e[prop.name].owner);
|
|
681
|
-
}
|
|
682
|
-
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
|
|
683
|
-
return filtered.reduce((a, b) => {
|
|
684
|
-
a.push(...b[prop.name].getItems());
|
|
685
|
-
return a;
|
|
686
|
-
}, []);
|
|
687
|
-
}
|
|
688
|
-
if (prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
689
|
-
// inverse side
|
|
690
|
-
return filtered;
|
|
691
|
-
}
|
|
692
|
-
// MANY_TO_ONE or ONE_TO_ONE
|
|
693
|
-
return this.filterReferences(entities, prop.name, options, ref);
|
|
667
|
+
if (filters) {
|
|
668
|
+
return this.#em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
|
|
694
669
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
670
|
+
return subCond;
|
|
671
|
+
}
|
|
672
|
+
buildFields(fields = [], prop, ref) {
|
|
673
|
+
if (ref) {
|
|
674
|
+
fields = prop.targetMeta.primaryKeys.map(targetPkName => `${prop.name}.${targetPkName}`);
|
|
700
675
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const
|
|
676
|
+
const ret = fields.reduce((ret, f) => {
|
|
677
|
+
if (Utils.isPlainObject(f)) {
|
|
678
|
+
Utils.keys(f)
|
|
679
|
+
.filter(ff => ff === prop.name)
|
|
680
|
+
.forEach(ff => ret.push(...f[ff]));
|
|
681
|
+
} else if (f.toString().includes('.')) {
|
|
682
|
+
const parts = f.toString().split('.');
|
|
683
|
+
const propName = parts.shift();
|
|
684
|
+
const childPropName = parts.join('.');
|
|
710
685
|
/* v8 ignore next */
|
|
711
|
-
if (
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
686
|
+
if (propName === prop.name) {
|
|
687
|
+
ret.push(childPropName);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return ret;
|
|
691
|
+
}, []);
|
|
692
|
+
// we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
|
|
693
|
+
if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
694
|
+
const owner = prop.targetMeta.properties[prop.mappedBy];
|
|
695
|
+
// when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
|
|
696
|
+
// otherwise the driver will exclude it and we won't be able to map children to their parent collections
|
|
697
|
+
if (owner && !ret.includes(owner.name) && (ret.length > 0 || owner.lazy)) {
|
|
698
|
+
ret.push(owner.name);
|
|
699
|
+
}
|
|
718
700
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
return [];
|
|
722
|
-
}
|
|
723
|
-
const children = entities.filter(e => Utils.isEntity(e[field], true));
|
|
724
|
-
if (options.refresh) {
|
|
725
|
-
return children.map(e => Reference.unwrapReference(e[field]));
|
|
726
|
-
}
|
|
727
|
-
if (options.fields) {
|
|
728
|
-
return children
|
|
729
|
-
.map(e => Reference.unwrapReference(e[field]))
|
|
730
|
-
.filter(target => {
|
|
731
|
-
const wrapped = helper(target);
|
|
732
|
-
const childFields = options.fields
|
|
733
|
-
.filter(f => f.startsWith(`${field}.`))
|
|
734
|
-
.map(f => f.substring(field.length + 1));
|
|
735
|
-
return !wrapped.__initialized || !childFields.every(cf => this.isPropertyLoaded(target, cf));
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
return children
|
|
739
|
-
.filter(e => !e[field].__helper.__initialized)
|
|
740
|
-
.map(e => Reference.unwrapReference(e[field]));
|
|
701
|
+
if (ret.length === 0) {
|
|
702
|
+
return undefined;
|
|
741
703
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
}
|
|
749
|
-
lookupAllRelationships(entityName) {
|
|
750
|
-
const ret = [];
|
|
751
|
-
const meta = this.#metadata.find(entityName);
|
|
752
|
-
meta.relations.forEach(prop => {
|
|
753
|
-
ret.push({
|
|
754
|
-
field: this.getRelationName(meta, prop),
|
|
755
|
-
// force select-in strategy when populating all relations as otherwise we could cause infinite loops when self-referencing
|
|
756
|
-
strategy: LoadStrategy.SELECT_IN,
|
|
757
|
-
// no need to look up populate children recursively as we just pass `all: true` here
|
|
758
|
-
all: true,
|
|
759
|
-
});
|
|
760
|
-
});
|
|
761
|
-
// For TPT parents with child types, keep an all:true sentinel so the populate
|
|
762
|
-
// loop doesn't exit early and the TPT child relation population can run after it.
|
|
763
|
-
if (ret.length === 0 && meta.inheritanceType === 'tpt' && meta.tptChildren?.length) {
|
|
764
|
-
ret.push({ field: meta.primaryKeys[0], strategy: LoadStrategy.SELECT_IN, all: true });
|
|
765
|
-
}
|
|
766
|
-
return ret;
|
|
704
|
+
return ret;
|
|
705
|
+
}
|
|
706
|
+
getChildReferences(entities, prop, options, ref) {
|
|
707
|
+
const filtered = this.filterCollections(entities, prop.name, options, ref);
|
|
708
|
+
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
709
|
+
return filtered.map(e => e[prop.name].owner);
|
|
767
710
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
711
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
|
|
712
|
+
return filtered.reduce((a, b) => {
|
|
713
|
+
a.push(...b[prop.name].getItems());
|
|
714
|
+
return a;
|
|
715
|
+
}, []);
|
|
773
716
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
717
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
718
|
+
// inverse side
|
|
719
|
+
return filtered;
|
|
720
|
+
}
|
|
721
|
+
// MANY_TO_ONE or ONE_TO_ONE
|
|
722
|
+
return this.filterReferences(entities, prop.name, options, ref);
|
|
723
|
+
}
|
|
724
|
+
filterCollections(entities, field, options, ref) {
|
|
725
|
+
if (options.refresh) {
|
|
726
|
+
return entities.filter(e => e[field]);
|
|
727
|
+
}
|
|
728
|
+
return entities.filter(e => Utils.isCollection(e[field]) && !e[field].isInitialized(!ref));
|
|
729
|
+
}
|
|
730
|
+
isPropertyLoaded(entity, field) {
|
|
731
|
+
if (!entity || field === '*') {
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
const wrapped = helper(entity);
|
|
735
|
+
if (!field.includes('.')) {
|
|
736
|
+
return wrapped.__loadedProperties.has(field);
|
|
737
|
+
}
|
|
738
|
+
const [f, ...r] = field.split('.');
|
|
739
|
+
/* v8 ignore next */
|
|
740
|
+
if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(wrapped.__meta.properties[f].kind)) {
|
|
744
|
+
return entity[f].getItems(false).every(item => this.isPropertyLoaded(item, r.join('.')));
|
|
745
|
+
}
|
|
746
|
+
return this.isPropertyLoaded(entity[f], r.join('.'));
|
|
747
|
+
}
|
|
748
|
+
filterReferences(entities, field, options, ref) {
|
|
749
|
+
if (ref) {
|
|
750
|
+
return [];
|
|
751
|
+
}
|
|
752
|
+
const children = entities.filter(e => Utils.isEntity(e[field], true));
|
|
753
|
+
if (options.refresh) {
|
|
754
|
+
return children.map(e => Reference.unwrapReference(e[field]));
|
|
755
|
+
}
|
|
756
|
+
if (options.fields) {
|
|
757
|
+
return children
|
|
758
|
+
.map(e => Reference.unwrapReference(e[field]))
|
|
759
|
+
.filter(target => {
|
|
760
|
+
const wrapped = helper(target);
|
|
761
|
+
const childFields = options.fields
|
|
762
|
+
.filter(f => f.startsWith(`${field}.`))
|
|
763
|
+
.map(f => f.substring(field.length + 1));
|
|
764
|
+
return !wrapped.__initialized || !childFields.every(cf => this.isPropertyLoaded(target, cf));
|
|
813
765
|
});
|
|
814
|
-
return ret;
|
|
815
766
|
}
|
|
767
|
+
return children.filter(e => !e[field].__helper.__initialized).map(e => Reference.unwrapReference(e[field]));
|
|
768
|
+
}
|
|
769
|
+
filterByReferences(entities, field, refresh) {
|
|
770
|
+
/* v8 ignore next */
|
|
771
|
+
if (refresh) {
|
|
772
|
+
return entities;
|
|
773
|
+
}
|
|
774
|
+
return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
|
|
775
|
+
}
|
|
776
|
+
lookupAllRelationships(entityName) {
|
|
777
|
+
const ret = [];
|
|
778
|
+
const meta = this.#metadata.find(entityName);
|
|
779
|
+
meta.relations.forEach(prop => {
|
|
780
|
+
ret.push({
|
|
781
|
+
field: this.getRelationName(meta, prop),
|
|
782
|
+
// force select-in strategy when populating all relations as otherwise we could cause infinite loops when self-referencing
|
|
783
|
+
strategy: LoadStrategy.SELECT_IN,
|
|
784
|
+
// no need to look up populate children recursively as we just pass `all: true` here
|
|
785
|
+
all: true,
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
// For TPT parents with child types, keep an all:true sentinel so the populate
|
|
789
|
+
// loop doesn't exit early and the TPT child relation population can run after it.
|
|
790
|
+
if (ret.length === 0 && meta.inheritanceType === 'tpt' && meta.tptChildren?.length) {
|
|
791
|
+
ret.push({ field: meta.primaryKeys[0], strategy: LoadStrategy.SELECT_IN, all: true });
|
|
792
|
+
}
|
|
793
|
+
return ret;
|
|
794
|
+
}
|
|
795
|
+
getRelationName(meta, prop) {
|
|
796
|
+
if (!prop.embedded) {
|
|
797
|
+
return prop.name;
|
|
798
|
+
}
|
|
799
|
+
return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
|
|
800
|
+
}
|
|
801
|
+
lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
|
|
802
|
+
const meta = this.#metadata.find(entityName);
|
|
803
|
+
if (!meta && !prefix) {
|
|
804
|
+
return populate;
|
|
805
|
+
}
|
|
806
|
+
if (!meta || visited.includes(meta)) {
|
|
807
|
+
return [];
|
|
808
|
+
}
|
|
809
|
+
visited.push(meta);
|
|
810
|
+
const ret = prefix === '' ? [...populate] : [];
|
|
811
|
+
meta.relations
|
|
812
|
+
.filter(prop => {
|
|
813
|
+
const field = this.getRelationName(meta, prop);
|
|
814
|
+
const prefixed = prefix ? `${prefix}.${field}` : field;
|
|
815
|
+
const isExcluded = exclude?.includes(prefixed);
|
|
816
|
+
const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
|
|
817
|
+
const populated = populate.some(p => p.field === prop.name);
|
|
818
|
+
const disabled = populate.some(p => p.field === prop.name && p.all === false);
|
|
819
|
+
return !disabled && !isExcluded && (eager || populated);
|
|
820
|
+
})
|
|
821
|
+
.forEach(prop => {
|
|
822
|
+
const field = this.getRelationName(meta, prop);
|
|
823
|
+
const prefixed = prefix ? `${prefix}.${field}` : field;
|
|
824
|
+
const nestedPopulate = populate
|
|
825
|
+
.filter(p => p.field === prop.name)
|
|
826
|
+
.flatMap(p => p.children)
|
|
827
|
+
.filter(Boolean);
|
|
828
|
+
const nested = this.lookupEagerLoadedRelationships(
|
|
829
|
+
prop.targetMeta.class,
|
|
830
|
+
nestedPopulate,
|
|
831
|
+
strategy,
|
|
832
|
+
prefixed,
|
|
833
|
+
visited.slice(),
|
|
834
|
+
exclude,
|
|
835
|
+
);
|
|
836
|
+
if (nested.length > 0) {
|
|
837
|
+
ret.push(...nested);
|
|
838
|
+
} else {
|
|
839
|
+
const selfReferencing =
|
|
840
|
+
[meta.tableName, ...visited.map(m => m.tableName)].includes(prop.targetMeta.tableName) && prop.eager;
|
|
841
|
+
ret.push({
|
|
842
|
+
field: prefixed,
|
|
843
|
+
// enforce select-in strategy for self-referencing relations
|
|
844
|
+
strategy: selfReferencing ? LoadStrategy.SELECT_IN : (strategy ?? prop.strategy),
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
return ret;
|
|
849
|
+
}
|
|
816
850
|
}
|