@mikro-orm/core 7.0.4-dev.9 → 7.0.4
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 +884 -583
- package/EntityManager.js +1922 -1895
- package/MikroORM.d.ts +103 -74
- package/MikroORM.js +178 -179
- 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 +440 -301
- package/entity/BaseEntity.d.ts +120 -83
- package/entity/BaseEntity.js +43 -43
- package/entity/Collection.d.ts +212 -179
- package/entity/Collection.js +727 -721
- package/entity/EntityAssigner.d.ts +88 -77
- package/entity/EntityAssigner.js +231 -230
- package/entity/EntityFactory.d.ts +66 -54
- package/entity/EntityFactory.js +425 -383
- package/entity/EntityHelper.d.ts +34 -22
- package/entity/EntityHelper.js +280 -267
- package/entity/EntityIdentifier.d.ts +4 -4
- package/entity/EntityIdentifier.js +10 -10
- package/entity/EntityLoader.d.ts +98 -72
- package/entity/EntityLoader.js +753 -723
- 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 +126 -82
- package/entity/Reference.js +278 -274
- package/entity/WrappedEntity.d.ts +115 -72
- package/entity/WrappedEntity.js +168 -166
- package/entity/defineEntity.d.ts +1315 -636
- 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 +481 -416
- 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 +211 -127
- package/metadata/EntitySchema.js +397 -398
- package/metadata/MetadataDiscovery.d.ts +114 -114
- package/metadata/MetadataDiscovery.js +1951 -1863
- package/metadata/MetadataProvider.d.ts +24 -21
- package/metadata/MetadataProvider.js +82 -84
- package/metadata/MetadataStorage.d.ts +38 -32
- package/metadata/MetadataStorage.js +118 -118
- package/metadata/MetadataValidator.d.ts +39 -39
- package/metadata/MetadataValidator.js +381 -338
- package/metadata/discover-entities.d.ts +5 -2
- package/metadata/discover-entities.js +35 -27
- 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 +310 -299
- package/platforms/Platform.js +663 -636
- 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 +1250 -737
- package/typings.js +244 -231
- 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 +178 -170
- package/unit-of-work/ChangeSetPersister.d.ts +63 -44
- package/unit-of-work/ChangeSetPersister.js +442 -421
- 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 +1200 -1183
- package/utils/AbstractMigrator.d.ts +111 -91
- package/utils/AbstractMigrator.js +275 -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 +98 -81
- package/utils/EntityComparator.js +828 -728
- package/utils/NullHighlighter.d.ts +1 -1
- package/utils/NullHighlighter.js +3 -3
- package/utils/QueryHelper.d.ts +79 -51
- package/utils/QueryHelper.js +372 -361
- package/utils/RawQueryFragment.d.ts +50 -34
- package/utils/RawQueryFragment.js +107 -105
- 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 +199 -197
- package/utils/Utils.d.ts +204 -145
- package/utils/Utils.js +812 -810
- package/utils/clone.js +104 -113
- package/utils/env-vars.js +90 -88
- package/utils/fs-utils.d.ts +15 -15
- package/utils/fs-utils.js +180 -181
- package/utils/upsert-utils.d.ts +20 -5
- package/utils/upsert-utils.js +114 -116
|
@@ -17,1193 +17,1210 @@ import { createAsyncContext } from '../utils/AsyncContext.js';
|
|
|
17
17
|
const insideFlush = createAsyncContext();
|
|
18
18
|
/** Implements the Unit of Work pattern: tracks entity changes, computes change sets, and flushes them to the database. */
|
|
19
19
|
export class UnitOfWork {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
data[key] = prop.customType.convertToDatabaseValue(converted, this.#platform, { key, mode: 'hydration' });
|
|
103
|
-
}
|
|
104
|
-
if (forceUndefined) {
|
|
105
|
-
if (data[key] === null) {
|
|
106
|
-
data[key] = undefined;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* @internal
|
|
113
|
-
*/
|
|
114
|
-
register(entity, data, options) {
|
|
115
|
-
this.#identityMap.store(entity);
|
|
116
|
-
EntityHelper.ensurePropagation(entity);
|
|
117
|
-
if (options?.newEntity) {
|
|
118
|
-
return entity;
|
|
119
|
-
}
|
|
120
|
-
const forceUndefined = this.#em.config.get('forceUndefined');
|
|
121
|
-
const wrapped = helper(entity);
|
|
122
|
-
if (options?.loaded && wrapped.__initialized && !wrapped.__onLoadFired) {
|
|
123
|
-
this.#loadedEntities.add(entity);
|
|
124
|
-
}
|
|
125
|
-
wrapped.__em ??= this.#em;
|
|
126
|
-
wrapped.__managed = true;
|
|
127
|
-
if (data && (options?.refresh || !wrapped.__originalEntityData)) {
|
|
128
|
-
this.normalizeEntityData(wrapped.__meta, data);
|
|
129
|
-
for (const key of Utils.keys(data)) {
|
|
130
|
-
const prop = wrapped.__meta.properties[key];
|
|
131
|
-
if (prop) {
|
|
132
|
-
wrapped.__loadedProperties.add(key);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
wrapped.__originalEntityData = data;
|
|
136
|
-
}
|
|
137
|
-
return entity;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* @internal
|
|
141
|
-
*/
|
|
142
|
-
async dispatchOnLoadEvent() {
|
|
143
|
-
for (const entity of this.#loadedEntities) {
|
|
144
|
-
if (this.#eventManager.hasListeners(EventType.onLoad, entity.__meta)) {
|
|
145
|
-
await this.#eventManager.dispatchEvent(EventType.onLoad, { entity, meta: entity.__meta, em: this.#em });
|
|
146
|
-
helper(entity).__onLoadFired = true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
this.#loadedEntities.clear();
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* @internal
|
|
153
|
-
*/
|
|
154
|
-
unmarkAsLoaded(entity) {
|
|
155
|
-
this.#loadedEntities.delete(entity);
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Returns entity from the identity map. For composite keys, you need to pass an array of PKs in the same order as they are defined in `meta.primaryKeys`.
|
|
159
|
-
*/
|
|
160
|
-
getById(entityName, id, schema, convertCustomTypes) {
|
|
161
|
-
if (id == null || (Array.isArray(id) && id.length === 0)) {
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
const meta = this.#metadata.find(entityName).root;
|
|
165
|
-
let hash;
|
|
166
|
-
if (meta.simplePK) {
|
|
167
|
-
hash = '' + id;
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
let keys = Array.isArray(id) ? Utils.flatten(id) : [id];
|
|
171
|
-
keys = meta.getPrimaryProps(true).map((p, i) => {
|
|
172
|
-
if (!convertCustomTypes && p.customType) {
|
|
173
|
-
return p.customType.convertToDatabaseValue(keys[i], this.#platform, {
|
|
174
|
-
key: p.name,
|
|
175
|
-
mode: 'hydration',
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
return keys[i];
|
|
179
|
-
});
|
|
180
|
-
hash = Utils.getPrimaryKeyHash(keys);
|
|
181
|
-
}
|
|
182
|
-
schema ??= meta.schema ?? this.#em.config.getSchema();
|
|
183
|
-
if (schema) {
|
|
184
|
-
hash = `${schema}:${hash}`;
|
|
185
|
-
}
|
|
186
|
-
return this.#identityMap.getByHash(meta, hash);
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Returns entity from the identity map by an alternate key (non-PK property).
|
|
190
|
-
* @param convertCustomTypes - If true, the value is in database format and will be converted to JS format for lookup.
|
|
191
|
-
* If false (default), the value is assumed to be in JS format already.
|
|
192
|
-
*/
|
|
193
|
-
getByKey(entityName, key, value, schema, convertCustomTypes) {
|
|
194
|
-
const meta = this.#metadata.find(entityName).root;
|
|
195
|
-
schema ??= meta.schema ?? this.#em.config.getSchema();
|
|
196
|
-
const prop = meta.properties[key];
|
|
197
|
-
// Convert from DB format to JS format if needed
|
|
198
|
-
if (convertCustomTypes && prop?.customType) {
|
|
199
|
-
value = prop.customType.convertToJSValue(value, this.#platform, { mode: 'hydration' });
|
|
200
|
-
}
|
|
201
|
-
const hash = this.#identityMap.getKeyHash(key, '' + value, schema);
|
|
202
|
-
return this.#identityMap.getByHash(meta, hash);
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Stores an entity in the identity map under an alternate key (non-PK property).
|
|
206
|
-
* Also sets the property value on the entity.
|
|
207
|
-
* @param convertCustomTypes - If true, the value is in database format and will be converted to JS format.
|
|
208
|
-
* If false (default), the value is assumed to be in JS format already.
|
|
209
|
-
*/
|
|
210
|
-
storeByKey(entity, key, value, schema, convertCustomTypes) {
|
|
211
|
-
const meta = entity.__meta.root;
|
|
212
|
-
schema ??= meta.schema ?? this.#em.config.getSchema();
|
|
213
|
-
const prop = meta.properties[key];
|
|
214
|
-
// Convert from DB format to JS format if needed
|
|
215
|
-
if (convertCustomTypes && prop?.customType) {
|
|
216
|
-
value = prop.customType.convertToJSValue(value, this.#platform, { mode: 'hydration' });
|
|
217
|
-
}
|
|
218
|
-
// Set the property on the entity
|
|
219
|
-
entity[key] = value;
|
|
220
|
-
this.#identityMap.storeByKey(entity, key, '' + value, schema);
|
|
221
|
-
}
|
|
222
|
-
/** Attempts to extract a primary key from the where condition and look up the entity in the identity map. */
|
|
223
|
-
tryGetById(entityName, where, schema, strict = true) {
|
|
224
|
-
const pk = Utils.extractPK(where, this.#metadata.find(entityName), strict);
|
|
225
|
-
if (!pk) {
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
return this.getById(entityName, pk, schema);
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Returns map of all managed entities.
|
|
232
|
-
*/
|
|
233
|
-
getIdentityMap() {
|
|
234
|
-
return this.#identityMap;
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Returns stored snapshot of entity state that is used for change set computation.
|
|
238
|
-
*/
|
|
239
|
-
getOriginalEntityData(entity) {
|
|
240
|
-
return helper(entity).__originalEntityData;
|
|
241
|
-
}
|
|
242
|
-
/** Returns the set of entities scheduled for persistence. */
|
|
243
|
-
getPersistStack() {
|
|
244
|
-
return this.#persistStack;
|
|
245
|
-
}
|
|
246
|
-
/** Returns the set of entities scheduled for removal. */
|
|
247
|
-
getRemoveStack() {
|
|
248
|
-
return this.#removeStack;
|
|
249
|
-
}
|
|
250
|
-
/** Returns all computed change sets for the current flush. */
|
|
251
|
-
getChangeSets() {
|
|
252
|
-
return [...this.#changeSets.values()];
|
|
253
|
-
}
|
|
254
|
-
/** Returns all M:N collections that need synchronization. */
|
|
255
|
-
getCollectionUpdates() {
|
|
256
|
-
return [...this.#collectionUpdates];
|
|
257
|
-
}
|
|
258
|
-
/** Returns extra updates needed for relations that could not be resolved in the initial pass. */
|
|
259
|
-
getExtraUpdates() {
|
|
260
|
-
return this.#extraUpdates;
|
|
261
|
-
}
|
|
262
|
-
/** Checks whether an auto-flush is needed before querying the given entity type. */
|
|
263
|
-
shouldAutoFlush(meta) {
|
|
264
|
-
if (insideFlush.getStore()) {
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
if (this.#queuedActions.has(meta.class) || this.#queuedActions.has(meta.root.class)) {
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
270
|
-
if (meta.discriminatorMap && Object.values(meta.discriminatorMap).some(v => this.#queuedActions.has(v))) {
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
275
|
-
/** Clears the queue of entity types that triggered auto-flush detection. */
|
|
276
|
-
clearActionsQueue() {
|
|
277
|
-
this.#queuedActions.clear();
|
|
278
|
-
}
|
|
279
|
-
/** Computes and registers a change set for the given entity. */
|
|
280
|
-
computeChangeSet(entity, type) {
|
|
281
|
-
const wrapped = helper(entity);
|
|
282
|
-
if (type === ChangeSetType.DELETE || type === ChangeSetType.DELETE_EARLY) {
|
|
283
|
-
this.#changeSets.set(entity, new ChangeSet(entity, type, {}, wrapped.__meta));
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
const cs = this.#changeSetComputer.computeChangeSet(entity);
|
|
287
|
-
if (!cs || this.checkUniqueProps(cs)) {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
/* v8 ignore next */
|
|
291
|
-
if (type) {
|
|
292
|
-
cs.type = type;
|
|
293
|
-
}
|
|
294
|
-
this.initIdentifier(entity);
|
|
295
|
-
this.#changeSets.set(entity, cs);
|
|
296
|
-
this.#persistStack.delete(entity);
|
|
297
|
-
wrapped.__originalEntityData = this.#comparator.prepareEntity(entity);
|
|
298
|
-
}
|
|
299
|
-
/** Recomputes and merges the change set for an already-tracked entity. */
|
|
300
|
-
recomputeSingleChangeSet(entity) {
|
|
301
|
-
const changeSet = this.#changeSets.get(entity);
|
|
302
|
-
if (!changeSet) {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
const cs = this.#changeSetComputer.computeChangeSet(entity);
|
|
306
|
-
if (cs && !this.checkUniqueProps(cs)) {
|
|
307
|
-
Object.assign(changeSet.payload, cs.payload);
|
|
308
|
-
helper(entity).__originalEntityData = this.#comparator.prepareEntity(entity);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/** Marks an entity for persistence, cascading to related entities. */
|
|
312
|
-
persist(entity, visited, options = {}) {
|
|
313
|
-
EntityHelper.ensurePropagation(entity);
|
|
314
|
-
if (options.checkRemoveStack && this.#removeStack.has(entity)) {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
const wrapped = helper(entity);
|
|
318
|
-
this.#persistStack.add(entity);
|
|
319
|
-
this.#queuedActions.add(wrapped.__meta.class);
|
|
320
|
-
this.#removeStack.delete(entity);
|
|
321
|
-
if (!wrapped.__managed && wrapped.hasPrimaryKey()) {
|
|
322
|
-
this.#identityMap.store(entity);
|
|
323
|
-
}
|
|
324
|
-
if (options.cascade ?? true) {
|
|
325
|
-
this.cascade(entity, Cascade.PERSIST, visited, options);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
/** Marks an entity for removal, cascading to related entities. */
|
|
329
|
-
remove(entity, visited, options = {}) {
|
|
330
|
-
// allow removing not managed entities if they are not part of the persist stack
|
|
331
|
-
if (helper(entity).__managed || !this.#persistStack.has(entity)) {
|
|
332
|
-
this.#removeStack.add(entity);
|
|
333
|
-
this.#queuedActions.add(helper(entity).__meta.class);
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
this.#persistStack.delete(entity);
|
|
337
|
-
this.#identityMap.delete(entity);
|
|
338
|
-
}
|
|
339
|
-
// remove from referencing relations that are nullable
|
|
340
|
-
for (const prop of helper(entity).__meta.bidirectionalRelations) {
|
|
341
|
-
const inverseProp = prop.mappedBy || prop.inversedBy;
|
|
342
|
-
const relation = Reference.unwrapReference(entity[prop.name]);
|
|
343
|
-
const prop2 = prop.targetMeta.properties[inverseProp];
|
|
344
|
-
if (prop.kind === ReferenceKind.ONE_TO_MANY && prop2.nullable && Utils.isCollection(relation)) {
|
|
345
|
-
for (const item of relation.getItems(false)) {
|
|
346
|
-
delete item[inverseProp];
|
|
347
|
-
}
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
const target = relation?.[inverseProp];
|
|
351
|
-
if (relation && Utils.isCollection(target)) {
|
|
352
|
-
target.removeWithoutPropagation(entity);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
if (options.cascade ?? true) {
|
|
356
|
-
this.cascade(entity, Cascade.REMOVE, visited);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
/** Flushes all pending changes to the database within a transaction. */
|
|
360
|
-
async commit() {
|
|
361
|
-
if (this.#working) {
|
|
362
|
-
if (insideFlush.getStore()) {
|
|
363
|
-
throw ValidationError.cannotCommit();
|
|
364
|
-
}
|
|
365
|
-
return new Promise((resolve, reject) => {
|
|
366
|
-
this.#flushQueue.push(() => {
|
|
367
|
-
return insideFlush.run(true, () => {
|
|
368
|
-
return this.doCommit().then(resolve, reject);
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
try {
|
|
374
|
-
this.#working = true;
|
|
375
|
-
await insideFlush.run(true, () => this.doCommit());
|
|
376
|
-
while (this.#flushQueue.length) {
|
|
377
|
-
await this.#flushQueue.shift()();
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
finally {
|
|
381
|
-
this.postCommitCleanup();
|
|
382
|
-
this.#working = false;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
async doCommit() {
|
|
386
|
-
const oldTx = this.#em.getTransactionContext();
|
|
387
|
-
try {
|
|
388
|
-
await this.#eventManager.dispatchEvent(EventType.beforeFlush, { em: this.#em, uow: this });
|
|
389
|
-
this.computeChangeSets();
|
|
390
|
-
for (const cs of this.#changeSets.values()) {
|
|
391
|
-
cs.entity.__helper.__processing = true;
|
|
392
|
-
}
|
|
393
|
-
await this.#eventManager.dispatchEvent(EventType.onFlush, { em: this.#em, uow: this });
|
|
394
|
-
this.filterCollectionUpdates();
|
|
395
|
-
// nothing to do, do not start transaction
|
|
396
|
-
if (this.#changeSets.size === 0 && this.#collectionUpdates.size === 0 && this.#extraUpdates.size === 0) {
|
|
397
|
-
await this.#eventManager.dispatchEvent(EventType.afterFlush, { em: this.#em, uow: this });
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
const groups = this.getChangeSetGroups();
|
|
401
|
-
const platform = this.#em.getPlatform();
|
|
402
|
-
const runInTransaction = !this.#em.isInTransaction() && platform.supportsTransactions() && this.#em.config.get('implicitTransactions');
|
|
403
|
-
if (runInTransaction) {
|
|
404
|
-
const loggerContext = Utils.merge({ id: this.#em._id }, this.#em.getLoggerContext({ disableContextResolution: true }));
|
|
405
|
-
await this.#em.getConnection('write').transactional(trx => this.persistToDatabase(groups, trx), {
|
|
406
|
-
ctx: oldTx,
|
|
407
|
-
eventBroadcaster: new TransactionEventBroadcaster(this.#em),
|
|
408
|
-
loggerContext,
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
else {
|
|
412
|
-
await this.persistToDatabase(groups, this.#em.getTransactionContext());
|
|
413
|
-
}
|
|
414
|
-
this.resetTransaction(oldTx);
|
|
415
|
-
for (const cs of this.#changeSets.values()) {
|
|
416
|
-
cs.entity.__helper.__processing = false;
|
|
417
|
-
}
|
|
418
|
-
await this.#eventManager.dispatchEvent(EventType.afterFlush, { em: this.#em, uow: this });
|
|
419
|
-
}
|
|
420
|
-
finally {
|
|
421
|
-
this.resetTransaction(oldTx);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
async lock(entity, options) {
|
|
425
|
-
if (!this.getById(entity.constructor, helper(entity).__primaryKeys, helper(entity).__schema)) {
|
|
426
|
-
throw ValidationError.entityNotManaged(entity);
|
|
427
|
-
}
|
|
428
|
-
const meta = this.#metadata.find(entity.constructor);
|
|
429
|
-
if (options.lockMode === LockMode.OPTIMISTIC) {
|
|
430
|
-
await this.lockOptimistic(entity, meta, options.lockVersion);
|
|
431
|
-
}
|
|
432
|
-
else if (options.lockMode != null) {
|
|
433
|
-
await this.lockPessimistic(entity, options);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
clear() {
|
|
437
|
-
this.#identityMap.clear();
|
|
438
|
-
this.#loadedEntities.clear();
|
|
439
|
-
this.postCommitCleanup();
|
|
440
|
-
}
|
|
441
|
-
unsetIdentity(entity) {
|
|
442
|
-
this.#identityMap.delete(entity);
|
|
443
|
-
const wrapped = helper(entity);
|
|
444
|
-
const serializedPK = wrapped.getSerializedPrimaryKey();
|
|
445
|
-
// remove references of this entity in all managed entities, otherwise flushing could reinsert the entity
|
|
446
|
-
for (const { meta, prop } of wrapped.__meta.referencingProperties) {
|
|
447
|
-
for (const referrer of this.#identityMap.getStore(meta).values()) {
|
|
448
|
-
const rel = Reference.unwrapReference(referrer[prop.name]);
|
|
449
|
-
if (Utils.isCollection(rel)) {
|
|
450
|
-
rel.removeWithoutPropagation(entity);
|
|
451
|
-
}
|
|
452
|
-
else if (rel &&
|
|
453
|
-
(prop.mapToPk
|
|
454
|
-
? helper(this.#em.getReference(prop.targetMeta.class, rel)).getSerializedPrimaryKey() === serializedPK
|
|
455
|
-
: rel === entity)) {
|
|
456
|
-
if (prop.formula) {
|
|
457
|
-
delete referrer[prop.name];
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
delete helper(referrer).__data[prop.name];
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
delete wrapped.__identifier;
|
|
466
|
-
delete wrapped.__originalEntityData;
|
|
467
|
-
wrapped.__managed = false;
|
|
468
|
-
}
|
|
469
|
-
computeChangeSets() {
|
|
470
|
-
this.#changeSets.clear();
|
|
471
|
-
const visited = new Set();
|
|
472
|
-
for (const entity of this.#removeStack) {
|
|
473
|
-
this.cascade(entity, Cascade.REMOVE, visited);
|
|
474
|
-
}
|
|
475
|
-
visited.clear();
|
|
476
|
-
for (const entity of this.#identityMap) {
|
|
477
|
-
if (!this.#removeStack.has(entity) && !this.#persistStack.has(entity) && !this.#orphanRemoveStack.has(entity)) {
|
|
478
|
-
this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
for (const entity of this.#persistStack) {
|
|
482
|
-
this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
|
|
483
|
-
}
|
|
484
|
-
visited.clear();
|
|
485
|
-
for (const entity of this.#persistStack) {
|
|
486
|
-
this.findNewEntities(entity, visited);
|
|
487
|
-
}
|
|
488
|
-
for (const entity of this.#orphanRemoveStack) {
|
|
489
|
-
if (!helper(entity).__processing) {
|
|
490
|
-
this.#removeStack.add(entity);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
// Check insert stack if there are any entities matching something from delete stack. This can happen when recreating entities.
|
|
494
|
-
const inserts = {};
|
|
495
|
-
for (const cs of this.#changeSets.values()) {
|
|
496
|
-
if (cs.type === ChangeSetType.CREATE) {
|
|
497
|
-
inserts[cs.meta.uniqueName] ??= [];
|
|
498
|
-
inserts[cs.meta.uniqueName].push(cs);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
for (const cs of this.#changeSets.values()) {
|
|
502
|
-
if (cs.type === ChangeSetType.UPDATE) {
|
|
503
|
-
this.findEarlyUpdates(cs, inserts[cs.meta.uniqueName]);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
for (const entity of this.#removeStack) {
|
|
507
|
-
const wrapped = helper(entity);
|
|
508
|
-
/* v8 ignore next */
|
|
509
|
-
if (wrapped.__processing) {
|
|
510
|
-
continue;
|
|
511
|
-
}
|
|
512
|
-
const deletePkHash = [wrapped.getSerializedPrimaryKey(), ...this.expandUniqueProps(entity)];
|
|
513
|
-
let type = ChangeSetType.DELETE;
|
|
514
|
-
for (const cs of inserts[wrapped.__meta.uniqueName] ?? []) {
|
|
515
|
-
if (deletePkHash.some(hash => hash === cs.getSerializedPrimaryKey() || this.expandUniqueProps(cs.entity).find(child => hash === child))) {
|
|
516
|
-
type = ChangeSetType.DELETE_EARLY;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
this.computeChangeSet(entity, type);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
scheduleExtraUpdate(changeSet, props) {
|
|
523
|
-
if (props.length === 0) {
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
let conflicts = false;
|
|
527
|
-
let type = ChangeSetType.UPDATE;
|
|
528
|
-
if (!props.some(prop => prop.name in changeSet.payload)) {
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
for (const cs of this.#changeSets.values()) {
|
|
532
|
-
for (const prop of props) {
|
|
533
|
-
if (prop.name in cs.payload && cs.rootMeta === changeSet.rootMeta && cs.type === changeSet.type) {
|
|
534
|
-
conflicts = true;
|
|
535
|
-
if (changeSet.payload[prop.name] == null) {
|
|
536
|
-
type = ChangeSetType.UPDATE_EARLY;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
if (!conflicts) {
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
this.#extraUpdates.add([
|
|
545
|
-
changeSet.entity,
|
|
546
|
-
props.map(p => p.name),
|
|
547
|
-
props.map(p => changeSet.entity[p.name]),
|
|
548
|
-
changeSet,
|
|
549
|
-
type,
|
|
550
|
-
]);
|
|
551
|
-
for (const p of props) {
|
|
552
|
-
delete changeSet.entity[p.name];
|
|
553
|
-
delete changeSet.payload[p.name];
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
scheduleOrphanRemoval(entity, visited) {
|
|
557
|
-
if (entity) {
|
|
558
|
-
const wrapped = helper(entity);
|
|
559
|
-
wrapped.__em = this.#em;
|
|
560
|
-
this.#orphanRemoveStack.add(entity);
|
|
561
|
-
this.#queuedActions.add(wrapped.__meta.class);
|
|
562
|
-
this.cascade(entity, Cascade.SCHEDULE_ORPHAN_REMOVAL, visited);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
cancelOrphanRemoval(entity, visited) {
|
|
566
|
-
this.#orphanRemoveStack.delete(entity);
|
|
567
|
-
this.cascade(entity, Cascade.CANCEL_ORPHAN_REMOVAL, visited);
|
|
568
|
-
}
|
|
569
|
-
getOrphanRemoveStack() {
|
|
570
|
-
return this.#orphanRemoveStack;
|
|
571
|
-
}
|
|
572
|
-
getChangeSetPersister() {
|
|
573
|
-
return this.#changeSetPersister;
|
|
574
|
-
}
|
|
575
|
-
findNewEntities(entity, visited, idx = 0, processed = new Set()) {
|
|
576
|
-
if (visited.has(entity)) {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
visited.add(entity);
|
|
580
|
-
processed.add(entity);
|
|
581
|
-
const wrapped = helper(entity);
|
|
582
|
-
if (wrapped.__processing || this.#removeStack.has(entity) || this.#orphanRemoveStack.has(entity)) {
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
// Set entityManager default schema
|
|
586
|
-
wrapped.__schema ??= this.#em.schema;
|
|
587
|
-
this.initIdentifier(entity);
|
|
588
|
-
for (const prop of wrapped.__meta.relations) {
|
|
589
|
-
const targets = Utils.unwrapProperty(entity, wrapped.__meta, prop);
|
|
590
|
-
for (const [target] of targets) {
|
|
591
|
-
const kind = Reference.unwrapReference(target);
|
|
592
|
-
this.processReference(entity, prop, kind, visited, processed, idx);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
const changeSet = this.#changeSetComputer.computeChangeSet(entity);
|
|
596
|
-
if (changeSet && !this.checkUniqueProps(changeSet)) {
|
|
597
|
-
// For TPT child entities, create changesets for each table in hierarchy
|
|
598
|
-
if (wrapped.__meta.inheritanceType === 'tpt' && wrapped.__meta.tptParent) {
|
|
599
|
-
this.createTPTChangeSets(entity, changeSet);
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
this.#changeSets.set(entity, changeSet);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* For TPT inheritance, creates separate changesets for each table in the hierarchy.
|
|
608
|
-
* Uses the same entity instance for all changesets - only the metadata and payload differ.
|
|
609
|
-
*/
|
|
610
|
-
createTPTChangeSets(entity, originalChangeSet) {
|
|
611
|
-
const meta = helper(entity).__meta;
|
|
612
|
-
const isCreate = originalChangeSet.type === ChangeSetType.CREATE;
|
|
613
|
-
let current = meta;
|
|
614
|
-
let leafCs;
|
|
615
|
-
const parentChangeSets = [];
|
|
616
|
-
while (current) {
|
|
617
|
-
const isRoot = !current.tptParent;
|
|
618
|
-
const payload = {};
|
|
619
|
-
for (const prop of current.ownProps) {
|
|
620
|
-
if (prop.name in originalChangeSet.payload) {
|
|
621
|
-
payload[prop.name] = originalChangeSet.payload[prop.name];
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
// For CREATE on non-root tables, include the PK (EntityIdentifier for deferred resolution)
|
|
625
|
-
if (isCreate && !isRoot) {
|
|
626
|
-
const wrapped = helper(entity);
|
|
627
|
-
const identifier = wrapped.__identifier;
|
|
628
|
-
const identifiers = Array.isArray(identifier) ? identifier : [identifier];
|
|
629
|
-
for (let i = 0; i < current.primaryKeys.length; i++) {
|
|
630
|
-
const pk = current.primaryKeys[i];
|
|
631
|
-
payload[pk] = identifiers[i] ?? originalChangeSet.payload[pk];
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
if (!isCreate && Object.keys(payload).length === 0) {
|
|
635
|
-
current = current.tptParent;
|
|
636
|
-
continue;
|
|
637
|
-
}
|
|
638
|
-
const cs = new ChangeSet(entity, originalChangeSet.type, payload, current);
|
|
639
|
-
if (current === meta) {
|
|
640
|
-
cs.originalEntity = originalChangeSet.originalEntity;
|
|
641
|
-
leafCs = cs;
|
|
642
|
-
}
|
|
643
|
-
else {
|
|
644
|
-
parentChangeSets.push(cs);
|
|
645
|
-
}
|
|
646
|
-
current = current.tptParent;
|
|
647
|
-
}
|
|
648
|
-
// When only parent properties changed (UPDATE), leaf payload is empty—create a stub anchor
|
|
649
|
-
if (!leafCs && parentChangeSets.length > 0) {
|
|
650
|
-
leafCs = new ChangeSet(entity, originalChangeSet.type, {}, meta);
|
|
651
|
-
leafCs.originalEntity = originalChangeSet.originalEntity;
|
|
652
|
-
}
|
|
653
|
-
// Store the leaf changeset in the main map (entity as key), with parent CSs attached
|
|
654
|
-
if (leafCs) {
|
|
655
|
-
if (parentChangeSets.length > 0) {
|
|
656
|
-
leafCs.tptChangeSets = parentChangeSets;
|
|
657
|
-
}
|
|
658
|
-
this.#changeSets.set(entity, leafCs);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Returns `true` when the change set should be skipped as it will be empty after the extra update.
|
|
663
|
-
*/
|
|
664
|
-
checkUniqueProps(changeSet) {
|
|
665
|
-
if (changeSet.type !== ChangeSetType.UPDATE) {
|
|
666
|
-
return false;
|
|
667
|
-
}
|
|
668
|
-
// when changing a unique nullable property (or a 1:1 relation), we can't do it in a single
|
|
669
|
-
// query as it would cause unique constraint violations
|
|
670
|
-
const uniqueProps = changeSet.meta.uniqueProps.filter(prop => {
|
|
671
|
-
return prop.nullable || changeSet.type !== ChangeSetType.CREATE;
|
|
20
|
+
/** map of references to managed entities */
|
|
21
|
+
#identityMap;
|
|
22
|
+
#persistStack = new Set();
|
|
23
|
+
#removeStack = new Set();
|
|
24
|
+
#orphanRemoveStack = new Set();
|
|
25
|
+
#changeSets = new Map();
|
|
26
|
+
#collectionUpdates = new Set();
|
|
27
|
+
#extraUpdates = new Set();
|
|
28
|
+
#metadata;
|
|
29
|
+
#platform;
|
|
30
|
+
#eventManager;
|
|
31
|
+
#comparator;
|
|
32
|
+
#changeSetComputer;
|
|
33
|
+
#changeSetPersister;
|
|
34
|
+
#queuedActions = new Set();
|
|
35
|
+
#loadedEntities = new Set();
|
|
36
|
+
#flushQueue = [];
|
|
37
|
+
#working = false;
|
|
38
|
+
#em;
|
|
39
|
+
constructor(em) {
|
|
40
|
+
this.#em = em;
|
|
41
|
+
this.#metadata = this.#em.getMetadata();
|
|
42
|
+
this.#platform = this.#em.getPlatform();
|
|
43
|
+
this.#identityMap = new IdentityMap(this.#platform.getDefaultSchemaName());
|
|
44
|
+
this.#eventManager = this.#em.getEventManager();
|
|
45
|
+
this.#comparator = this.#em.getComparator();
|
|
46
|
+
this.#changeSetComputer = new ChangeSetComputer(this.#em, this.#collectionUpdates);
|
|
47
|
+
this.#changeSetPersister = new ChangeSetPersister(this.#em);
|
|
48
|
+
}
|
|
49
|
+
/** Merges an entity into the identity map, taking a snapshot of its current state. */
|
|
50
|
+
merge(entity, visited) {
|
|
51
|
+
const wrapped = helper(entity);
|
|
52
|
+
wrapped.__em = this.#em;
|
|
53
|
+
if (!wrapped.hasPrimaryKey()) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// skip new entities that could be linked from already persisted entity
|
|
57
|
+
// that is being re-fetched (but allow calling `merge(e)` explicitly for those)
|
|
58
|
+
if (!wrapped.__managed && visited) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.#identityMap.store(entity);
|
|
62
|
+
// if visited is available, we are cascading, and need to be careful when resetting the entity data
|
|
63
|
+
// as there can be some entity with already changed state that is not yet flushed
|
|
64
|
+
if (wrapped.__initialized && (!visited || !wrapped.__originalEntityData)) {
|
|
65
|
+
wrapped.__originalEntityData = this.#comparator.prepareEntity(entity);
|
|
66
|
+
}
|
|
67
|
+
this.cascade(entity, Cascade.MERGE, visited ?? new Set());
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Entity data can wary in its shape, e.g. we might get a deep relation graph with joined strategy, but for diffing,
|
|
71
|
+
* we need to normalize the shape, so relation values are only raw FKs. This method handles that.
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
normalizeEntityData(meta, data) {
|
|
75
|
+
const forceUndefined = this.#em.config.get('forceUndefined');
|
|
76
|
+
for (const key of Utils.keys(data)) {
|
|
77
|
+
const prop = meta.properties[key];
|
|
78
|
+
if (!prop) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
83
|
+
Utils.isPlainObject(data[prop.name])
|
|
84
|
+
) {
|
|
85
|
+
// Skip polymorphic relations - they use PolymorphicRef wrapper
|
|
86
|
+
if (!prop.polymorphic) {
|
|
87
|
+
data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
|
|
88
|
+
}
|
|
89
|
+
} else if (prop.kind === ReferenceKind.EMBEDDED && !prop.object && Utils.isPlainObject(data[prop.name])) {
|
|
90
|
+
for (const p of prop.targetMeta.props) {
|
|
91
|
+
/* v8 ignore next */
|
|
92
|
+
const prefix = prop.prefix === false ? '' : prop.prefix === true ? prop.name + '_' : prop.prefix;
|
|
93
|
+
data[prefix + p.name] = data[prop.name][p.name];
|
|
94
|
+
}
|
|
95
|
+
data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
|
|
96
|
+
}
|
|
97
|
+
if (prop.hydrate === false && prop.customType?.ensureComparable(meta, prop)) {
|
|
98
|
+
const converted = prop.customType.convertToJSValue(data[key], this.#platform, {
|
|
99
|
+
key,
|
|
100
|
+
mode: 'hydration',
|
|
101
|
+
force: true,
|
|
672
102
|
});
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
103
|
+
data[key] = prop.customType.convertToDatabaseValue(converted, this.#platform, { key, mode: 'hydration' });
|
|
104
|
+
}
|
|
105
|
+
if (forceUndefined) {
|
|
106
|
+
if (data[key] === null) {
|
|
107
|
+
data[key] = undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
register(entity, data, options) {
|
|
116
|
+
this.#identityMap.store(entity);
|
|
117
|
+
EntityHelper.ensurePropagation(entity);
|
|
118
|
+
if (options?.newEntity) {
|
|
119
|
+
return entity;
|
|
120
|
+
}
|
|
121
|
+
const forceUndefined = this.#em.config.get('forceUndefined');
|
|
122
|
+
const wrapped = helper(entity);
|
|
123
|
+
if (options?.loaded && wrapped.__initialized && !wrapped.__onLoadFired) {
|
|
124
|
+
this.#loadedEntities.add(entity);
|
|
125
|
+
}
|
|
126
|
+
wrapped.__em ??= this.#em;
|
|
127
|
+
wrapped.__managed = true;
|
|
128
|
+
if (data && (options?.refresh || !wrapped.__originalEntityData)) {
|
|
129
|
+
this.normalizeEntityData(wrapped.__meta, data);
|
|
130
|
+
for (const key of Utils.keys(data)) {
|
|
131
|
+
const prop = wrapped.__meta.properties[key];
|
|
132
|
+
if (prop) {
|
|
133
|
+
wrapped.__loadedProperties.add(key);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
wrapped.__originalEntityData = data;
|
|
137
|
+
}
|
|
138
|
+
return entity;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
async dispatchOnLoadEvent() {
|
|
144
|
+
for (const entity of this.#loadedEntities) {
|
|
145
|
+
if (this.#eventManager.hasListeners(EventType.onLoad, entity.__meta)) {
|
|
146
|
+
await this.#eventManager.dispatchEvent(EventType.onLoad, { entity, meta: entity.__meta, em: this.#em });
|
|
147
|
+
helper(entity).__onLoadFired = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.#loadedEntities.clear();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* @internal
|
|
154
|
+
*/
|
|
155
|
+
unmarkAsLoaded(entity) {
|
|
156
|
+
this.#loadedEntities.delete(entity);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Returns entity from the identity map. For composite keys, you need to pass an array of PKs in the same order as they are defined in `meta.primaryKeys`.
|
|
160
|
+
*/
|
|
161
|
+
getById(entityName, id, schema, convertCustomTypes) {
|
|
162
|
+
if (id == null || (Array.isArray(id) && id.length === 0)) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
const meta = this.#metadata.find(entityName).root;
|
|
166
|
+
let hash;
|
|
167
|
+
if (meta.simplePK) {
|
|
168
|
+
hash = '' + id;
|
|
169
|
+
} else {
|
|
170
|
+
let keys = Array.isArray(id) ? Utils.flatten(id) : [id];
|
|
171
|
+
keys = meta.getPrimaryProps(true).map((p, i) => {
|
|
172
|
+
if (!convertCustomTypes && p.customType) {
|
|
173
|
+
return p.customType.convertToDatabaseValue(keys[i], this.#platform, {
|
|
174
|
+
key: p.name,
|
|
175
|
+
mode: 'hydration',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return keys[i];
|
|
179
|
+
});
|
|
180
|
+
hash = Utils.getPrimaryKeyHash(keys);
|
|
181
|
+
}
|
|
182
|
+
schema ??= meta.schema ?? this.#em.config.getSchema();
|
|
183
|
+
if (schema) {
|
|
184
|
+
hash = `${schema}:${hash}`;
|
|
185
|
+
}
|
|
186
|
+
return this.#identityMap.getByHash(meta, hash);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns entity from the identity map by an alternate key (non-PK property).
|
|
190
|
+
* @param convertCustomTypes - If true, the value is in database format and will be converted to JS format for lookup.
|
|
191
|
+
* If false (default), the value is assumed to be in JS format already.
|
|
192
|
+
*/
|
|
193
|
+
getByKey(entityName, key, value, schema, convertCustomTypes) {
|
|
194
|
+
const meta = this.#metadata.find(entityName).root;
|
|
195
|
+
schema ??= meta.schema ?? this.#em.config.getSchema();
|
|
196
|
+
const prop = meta.properties[key];
|
|
197
|
+
// Convert from DB format to JS format if needed
|
|
198
|
+
if (convertCustomTypes && prop?.customType) {
|
|
199
|
+
value = prop.customType.convertToJSValue(value, this.#platform, { mode: 'hydration' });
|
|
200
|
+
}
|
|
201
|
+
const hash = this.#identityMap.getKeyHash(key, '' + value, schema);
|
|
202
|
+
return this.#identityMap.getByHash(meta, hash);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Stores an entity in the identity map under an alternate key (non-PK property).
|
|
206
|
+
* Also sets the property value on the entity.
|
|
207
|
+
* @param convertCustomTypes - If true, the value is in database format and will be converted to JS format.
|
|
208
|
+
* If false (default), the value is assumed to be in JS format already.
|
|
209
|
+
*/
|
|
210
|
+
storeByKey(entity, key, value, schema, convertCustomTypes) {
|
|
211
|
+
const meta = entity.__meta.root;
|
|
212
|
+
schema ??= meta.schema ?? this.#em.config.getSchema();
|
|
213
|
+
const prop = meta.properties[key];
|
|
214
|
+
// Convert from DB format to JS format if needed
|
|
215
|
+
if (convertCustomTypes && prop?.customType) {
|
|
216
|
+
value = prop.customType.convertToJSValue(value, this.#platform, { mode: 'hydration' });
|
|
217
|
+
}
|
|
218
|
+
// Set the property on the entity
|
|
219
|
+
entity[key] = value;
|
|
220
|
+
this.#identityMap.storeByKey(entity, key, '' + value, schema);
|
|
221
|
+
}
|
|
222
|
+
/** Attempts to extract a primary key from the where condition and look up the entity in the identity map. */
|
|
223
|
+
tryGetById(entityName, where, schema, strict = true) {
|
|
224
|
+
const pk = Utils.extractPK(where, this.#metadata.find(entityName), strict);
|
|
225
|
+
if (!pk) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return this.getById(entityName, pk, schema);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Returns map of all managed entities.
|
|
232
|
+
*/
|
|
233
|
+
getIdentityMap() {
|
|
234
|
+
return this.#identityMap;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Returns stored snapshot of entity state that is used for change set computation.
|
|
238
|
+
*/
|
|
239
|
+
getOriginalEntityData(entity) {
|
|
240
|
+
return helper(entity).__originalEntityData;
|
|
241
|
+
}
|
|
242
|
+
/** Returns the set of entities scheduled for persistence. */
|
|
243
|
+
getPersistStack() {
|
|
244
|
+
return this.#persistStack;
|
|
245
|
+
}
|
|
246
|
+
/** Returns the set of entities scheduled for removal. */
|
|
247
|
+
getRemoveStack() {
|
|
248
|
+
return this.#removeStack;
|
|
249
|
+
}
|
|
250
|
+
/** Returns all computed change sets for the current flush. */
|
|
251
|
+
getChangeSets() {
|
|
252
|
+
return [...this.#changeSets.values()];
|
|
253
|
+
}
|
|
254
|
+
/** Returns all M:N collections that need synchronization. */
|
|
255
|
+
getCollectionUpdates() {
|
|
256
|
+
return [...this.#collectionUpdates];
|
|
257
|
+
}
|
|
258
|
+
/** Returns extra updates needed for relations that could not be resolved in the initial pass. */
|
|
259
|
+
getExtraUpdates() {
|
|
260
|
+
return this.#extraUpdates;
|
|
261
|
+
}
|
|
262
|
+
/** Checks whether an auto-flush is needed before querying the given entity type. */
|
|
263
|
+
shouldAutoFlush(meta) {
|
|
264
|
+
if (insideFlush.getStore()) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (this.#queuedActions.has(meta.class) || this.#queuedActions.has(meta.root.class)) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
if (meta.discriminatorMap && Object.values(meta.discriminatorMap).some(v => this.#queuedActions.has(v))) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
/** Clears the queue of entity types that triggered auto-flush detection. */
|
|
276
|
+
clearActionsQueue() {
|
|
277
|
+
this.#queuedActions.clear();
|
|
278
|
+
}
|
|
279
|
+
/** Computes and registers a change set for the given entity. */
|
|
280
|
+
computeChangeSet(entity, type) {
|
|
281
|
+
const wrapped = helper(entity);
|
|
282
|
+
if (type === ChangeSetType.DELETE || type === ChangeSetType.DELETE_EARLY) {
|
|
283
|
+
this.#changeSets.set(entity, new ChangeSet(entity, type, {}, wrapped.__meta));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const cs = this.#changeSetComputer.computeChangeSet(entity);
|
|
287
|
+
if (!cs || this.checkUniqueProps(cs)) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
/* v8 ignore next */
|
|
291
|
+
if (type) {
|
|
292
|
+
cs.type = type;
|
|
293
|
+
}
|
|
294
|
+
this.initIdentifier(entity);
|
|
295
|
+
this.#changeSets.set(entity, cs);
|
|
296
|
+
this.#persistStack.delete(entity);
|
|
297
|
+
wrapped.__originalEntityData = this.#comparator.prepareEntity(entity);
|
|
298
|
+
}
|
|
299
|
+
/** Recomputes and merges the change set for an already-tracked entity. */
|
|
300
|
+
recomputeSingleChangeSet(entity) {
|
|
301
|
+
const changeSet = this.#changeSets.get(entity);
|
|
302
|
+
if (!changeSet) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const cs = this.#changeSetComputer.computeChangeSet(entity);
|
|
306
|
+
if (cs && !this.checkUniqueProps(cs)) {
|
|
307
|
+
Object.assign(changeSet.payload, cs.payload);
|
|
308
|
+
helper(entity).__originalEntityData = this.#comparator.prepareEntity(entity);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** Marks an entity for persistence, cascading to related entities. */
|
|
312
|
+
persist(entity, visited, options = {}) {
|
|
313
|
+
EntityHelper.ensurePropagation(entity);
|
|
314
|
+
if (options.checkRemoveStack && this.#removeStack.has(entity)) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const wrapped = helper(entity);
|
|
318
|
+
this.#persistStack.add(entity);
|
|
319
|
+
this.#queuedActions.add(wrapped.__meta.class);
|
|
320
|
+
this.#removeStack.delete(entity);
|
|
321
|
+
if (!wrapped.__managed && wrapped.hasPrimaryKey()) {
|
|
322
|
+
this.#identityMap.store(entity);
|
|
323
|
+
}
|
|
324
|
+
if (options.cascade ?? true) {
|
|
325
|
+
this.cascade(entity, Cascade.PERSIST, visited, options);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/** Marks an entity for removal, cascading to related entities. */
|
|
329
|
+
remove(entity, visited, options = {}) {
|
|
330
|
+
// allow removing not managed entities if they are not part of the persist stack
|
|
331
|
+
if (helper(entity).__managed || !this.#persistStack.has(entity)) {
|
|
332
|
+
this.#removeStack.add(entity);
|
|
333
|
+
this.#queuedActions.add(helper(entity).__meta.class);
|
|
334
|
+
} else {
|
|
335
|
+
this.#persistStack.delete(entity);
|
|
336
|
+
this.#identityMap.delete(entity);
|
|
337
|
+
}
|
|
338
|
+
// remove from referencing relations that are nullable
|
|
339
|
+
for (const prop of helper(entity).__meta.bidirectionalRelations) {
|
|
340
|
+
const inverseProp = prop.mappedBy || prop.inversedBy;
|
|
341
|
+
const relation = Reference.unwrapReference(entity[prop.name]);
|
|
342
|
+
const prop2 = prop.targetMeta.properties[inverseProp];
|
|
343
|
+
if (prop.kind === ReferenceKind.ONE_TO_MANY && prop2.nullable && Utils.isCollection(relation)) {
|
|
344
|
+
for (const item of relation.getItems(false)) {
|
|
345
|
+
delete item[inverseProp];
|
|
346
|
+
}
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const target = relation?.[inverseProp];
|
|
350
|
+
if (relation && Utils.isCollection(target)) {
|
|
351
|
+
target.removeWithoutPropagation(entity);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (options.cascade ?? true) {
|
|
355
|
+
this.cascade(entity, Cascade.REMOVE, visited);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/** Flushes all pending changes to the database within a transaction. */
|
|
359
|
+
async commit() {
|
|
360
|
+
if (this.#working) {
|
|
361
|
+
if (insideFlush.getStore()) {
|
|
362
|
+
throw ValidationError.cannotCommit();
|
|
363
|
+
}
|
|
364
|
+
return new Promise((resolve, reject) => {
|
|
365
|
+
this.#flushQueue.push(() => {
|
|
366
|
+
return insideFlush.run(true, () => {
|
|
367
|
+
return this.doCommit().then(resolve, reject);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
this.#working = true;
|
|
374
|
+
await insideFlush.run(true, () => this.doCommit());
|
|
375
|
+
while (this.#flushQueue.length) {
|
|
376
|
+
await this.#flushQueue.shift()();
|
|
377
|
+
}
|
|
378
|
+
} finally {
|
|
379
|
+
this.postCommitCleanup();
|
|
380
|
+
this.#working = false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async doCommit() {
|
|
384
|
+
const oldTx = this.#em.getTransactionContext();
|
|
385
|
+
try {
|
|
386
|
+
await this.#eventManager.dispatchEvent(EventType.beforeFlush, { em: this.#em, uow: this });
|
|
387
|
+
this.computeChangeSets();
|
|
388
|
+
for (const cs of this.#changeSets.values()) {
|
|
389
|
+
cs.entity.__helper.__processing = true;
|
|
390
|
+
}
|
|
391
|
+
await this.#eventManager.dispatchEvent(EventType.onFlush, { em: this.#em, uow: this });
|
|
392
|
+
this.filterCollectionUpdates();
|
|
393
|
+
// nothing to do, do not start transaction
|
|
394
|
+
if (this.#changeSets.size === 0 && this.#collectionUpdates.size === 0 && this.#extraUpdates.size === 0) {
|
|
395
|
+
await this.#eventManager.dispatchEvent(EventType.afterFlush, { em: this.#em, uow: this });
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const groups = this.getChangeSetGroups();
|
|
399
|
+
const platform = this.#em.getPlatform();
|
|
400
|
+
const runInTransaction =
|
|
401
|
+
!this.#em.isInTransaction() && platform.supportsTransactions() && this.#em.config.get('implicitTransactions');
|
|
402
|
+
if (runInTransaction) {
|
|
403
|
+
const loggerContext = Utils.merge(
|
|
404
|
+
{ id: this.#em._id },
|
|
405
|
+
this.#em.getLoggerContext({ disableContextResolution: true }),
|
|
406
|
+
);
|
|
407
|
+
await this.#em.getConnection('write').transactional(trx => this.persistToDatabase(groups, trx), {
|
|
408
|
+
ctx: oldTx,
|
|
409
|
+
eventBroadcaster: new TransactionEventBroadcaster(this.#em),
|
|
410
|
+
loggerContext,
|
|
411
|
+
});
|
|
412
|
+
} else {
|
|
413
|
+
await this.persistToDatabase(groups, this.#em.getTransactionContext());
|
|
414
|
+
}
|
|
415
|
+
this.resetTransaction(oldTx);
|
|
416
|
+
for (const cs of this.#changeSets.values()) {
|
|
417
|
+
cs.entity.__helper.__processing = false;
|
|
418
|
+
}
|
|
419
|
+
await this.#eventManager.dispatchEvent(EventType.afterFlush, { em: this.#em, uow: this });
|
|
420
|
+
} finally {
|
|
421
|
+
this.resetTransaction(oldTx);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async lock(entity, options) {
|
|
425
|
+
if (!this.getById(entity.constructor, helper(entity).__primaryKeys, helper(entity).__schema)) {
|
|
426
|
+
throw ValidationError.entityNotManaged(entity);
|
|
427
|
+
}
|
|
428
|
+
const meta = this.#metadata.find(entity.constructor);
|
|
429
|
+
if (options.lockMode === LockMode.OPTIMISTIC) {
|
|
430
|
+
await this.lockOptimistic(entity, meta, options.lockVersion);
|
|
431
|
+
} else if (options.lockMode != null) {
|
|
432
|
+
await this.lockPessimistic(entity, options);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
clear() {
|
|
436
|
+
this.#identityMap.clear();
|
|
437
|
+
this.#loadedEntities.clear();
|
|
438
|
+
this.postCommitCleanup();
|
|
439
|
+
}
|
|
440
|
+
unsetIdentity(entity) {
|
|
441
|
+
this.#identityMap.delete(entity);
|
|
442
|
+
const wrapped = helper(entity);
|
|
443
|
+
const serializedPK = wrapped.getSerializedPrimaryKey();
|
|
444
|
+
// remove references of this entity in all managed entities, otherwise flushing could reinsert the entity
|
|
445
|
+
for (const { meta, prop } of wrapped.__meta.referencingProperties) {
|
|
446
|
+
for (const referrer of this.#identityMap.getStore(meta).values()) {
|
|
447
|
+
const rel = Reference.unwrapReference(referrer[prop.name]);
|
|
448
|
+
if (Utils.isCollection(rel)) {
|
|
449
|
+
rel.removeWithoutPropagation(entity);
|
|
450
|
+
} else if (
|
|
451
|
+
rel &&
|
|
452
|
+
(prop.mapToPk
|
|
453
|
+
? helper(this.#em.getReference(prop.targetMeta.class, rel)).getSerializedPrimaryKey() === serializedPK
|
|
454
|
+
: rel === entity)
|
|
455
|
+
) {
|
|
456
|
+
if (prop.formula) {
|
|
457
|
+
delete referrer[prop.name];
|
|
458
|
+
} else {
|
|
459
|
+
delete helper(referrer).__data[prop.name];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
delete wrapped.__identifier;
|
|
465
|
+
delete wrapped.__originalEntityData;
|
|
466
|
+
wrapped.__managed = false;
|
|
467
|
+
}
|
|
468
|
+
computeChangeSets() {
|
|
469
|
+
this.#changeSets.clear();
|
|
470
|
+
const visited = new Set();
|
|
471
|
+
for (const entity of this.#removeStack) {
|
|
472
|
+
this.cascade(entity, Cascade.REMOVE, visited);
|
|
473
|
+
}
|
|
474
|
+
visited.clear();
|
|
475
|
+
for (const entity of this.#identityMap) {
|
|
476
|
+
if (!this.#removeStack.has(entity) && !this.#persistStack.has(entity) && !this.#orphanRemoveStack.has(entity)) {
|
|
477
|
+
this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
for (const entity of this.#persistStack) {
|
|
481
|
+
this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
|
|
482
|
+
}
|
|
483
|
+
visited.clear();
|
|
484
|
+
for (const entity of this.#persistStack) {
|
|
485
|
+
this.findNewEntities(entity, visited);
|
|
486
|
+
}
|
|
487
|
+
for (const entity of this.#orphanRemoveStack) {
|
|
488
|
+
if (!helper(entity).__processing) {
|
|
489
|
+
this.#removeStack.add(entity);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// Check insert stack if there are any entities matching something from delete stack. This can happen when recreating entities.
|
|
493
|
+
const inserts = {};
|
|
494
|
+
for (const cs of this.#changeSets.values()) {
|
|
495
|
+
if (cs.type === ChangeSetType.CREATE) {
|
|
496
|
+
inserts[cs.meta.uniqueName] ??= [];
|
|
497
|
+
inserts[cs.meta.uniqueName].push(cs);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
for (const cs of this.#changeSets.values()) {
|
|
501
|
+
if (cs.type === ChangeSetType.UPDATE) {
|
|
502
|
+
this.findEarlyUpdates(cs, inserts[cs.meta.uniqueName]);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
for (const entity of this.#removeStack) {
|
|
506
|
+
const wrapped = helper(entity);
|
|
507
|
+
/* v8 ignore next */
|
|
508
|
+
if (wrapped.__processing) {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const deletePkHash = [wrapped.getSerializedPrimaryKey(), ...this.expandUniqueProps(entity)];
|
|
512
|
+
let type = ChangeSetType.DELETE;
|
|
513
|
+
for (const cs of inserts[wrapped.__meta.uniqueName] ?? []) {
|
|
514
|
+
if (
|
|
515
|
+
deletePkHash.some(
|
|
516
|
+
hash =>
|
|
517
|
+
hash === cs.getSerializedPrimaryKey() || this.expandUniqueProps(cs.entity).find(child => hash === child),
|
|
518
|
+
)
|
|
519
|
+
) {
|
|
520
|
+
type = ChangeSetType.DELETE_EARLY;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
this.computeChangeSet(entity, type);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
scheduleExtraUpdate(changeSet, props) {
|
|
527
|
+
if (props.length === 0) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
let conflicts = false;
|
|
531
|
+
let type = ChangeSetType.UPDATE;
|
|
532
|
+
if (!props.some(prop => prop.name in changeSet.payload)) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
for (const cs of this.#changeSets.values()) {
|
|
536
|
+
for (const prop of props) {
|
|
537
|
+
if (prop.name in cs.payload && cs.rootMeta === changeSet.rootMeta && cs.type === changeSet.type) {
|
|
538
|
+
conflicts = true;
|
|
539
|
+
if (changeSet.payload[prop.name] == null) {
|
|
540
|
+
type = ChangeSetType.UPDATE_EARLY;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (!conflicts) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
this.#extraUpdates.add([
|
|
549
|
+
changeSet.entity,
|
|
550
|
+
props.map(p => p.name),
|
|
551
|
+
props.map(p => changeSet.entity[p.name]),
|
|
552
|
+
changeSet,
|
|
553
|
+
type,
|
|
554
|
+
]);
|
|
555
|
+
for (const p of props) {
|
|
556
|
+
delete changeSet.entity[p.name];
|
|
557
|
+
delete changeSet.payload[p.name];
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
scheduleOrphanRemoval(entity, visited) {
|
|
561
|
+
if (entity) {
|
|
562
|
+
const wrapped = helper(entity);
|
|
563
|
+
wrapped.__em = this.#em;
|
|
564
|
+
this.#orphanRemoveStack.add(entity);
|
|
565
|
+
this.#queuedActions.add(wrapped.__meta.class);
|
|
566
|
+
this.cascade(entity, Cascade.SCHEDULE_ORPHAN_REMOVAL, visited);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
cancelOrphanRemoval(entity, visited) {
|
|
570
|
+
this.#orphanRemoveStack.delete(entity);
|
|
571
|
+
this.cascade(entity, Cascade.CANCEL_ORPHAN_REMOVAL, visited);
|
|
572
|
+
}
|
|
573
|
+
getOrphanRemoveStack() {
|
|
574
|
+
return this.#orphanRemoveStack;
|
|
575
|
+
}
|
|
576
|
+
getChangeSetPersister() {
|
|
577
|
+
return this.#changeSetPersister;
|
|
578
|
+
}
|
|
579
|
+
findNewEntities(entity, visited, idx = 0, processed = new Set()) {
|
|
580
|
+
if (visited.has(entity)) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
visited.add(entity);
|
|
584
|
+
processed.add(entity);
|
|
585
|
+
const wrapped = helper(entity);
|
|
586
|
+
if (wrapped.__processing || this.#removeStack.has(entity) || this.#orphanRemoveStack.has(entity)) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
// Set entityManager default schema
|
|
590
|
+
wrapped.__schema ??= this.#em.schema;
|
|
591
|
+
this.initIdentifier(entity);
|
|
592
|
+
for (const prop of wrapped.__meta.relations) {
|
|
593
|
+
const targets = Utils.unwrapProperty(entity, wrapped.__meta, prop);
|
|
594
|
+
for (const [target] of targets) {
|
|
595
|
+
const kind = Reference.unwrapReference(target);
|
|
596
|
+
this.processReference(entity, prop, kind, visited, processed, idx);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const changeSet = this.#changeSetComputer.computeChangeSet(entity);
|
|
600
|
+
if (changeSet && !this.checkUniqueProps(changeSet)) {
|
|
601
|
+
// For TPT child entities, create changesets for each table in hierarchy
|
|
602
|
+
if (wrapped.__meta.inheritanceType === 'tpt' && wrapped.__meta.tptParent) {
|
|
603
|
+
this.createTPTChangeSets(entity, changeSet);
|
|
604
|
+
} else {
|
|
605
|
+
this.#changeSets.set(entity, changeSet);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* For TPT inheritance, creates separate changesets for each table in the hierarchy.
|
|
611
|
+
* Uses the same entity instance for all changesets - only the metadata and payload differ.
|
|
612
|
+
*/
|
|
613
|
+
createTPTChangeSets(entity, originalChangeSet) {
|
|
614
|
+
const meta = helper(entity).__meta;
|
|
615
|
+
const isCreate = originalChangeSet.type === ChangeSetType.CREATE;
|
|
616
|
+
let current = meta;
|
|
617
|
+
let leafCs;
|
|
618
|
+
const parentChangeSets = [];
|
|
619
|
+
while (current) {
|
|
620
|
+
const isRoot = !current.tptParent;
|
|
621
|
+
const payload = {};
|
|
622
|
+
for (const prop of current.ownProps) {
|
|
623
|
+
if (prop.name in originalChangeSet.payload) {
|
|
624
|
+
payload[prop.name] = originalChangeSet.payload[prop.name];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// For CREATE on non-root tables, include the PK (EntityIdentifier for deferred resolution)
|
|
628
|
+
if (isCreate && !isRoot) {
|
|
880
629
|
const wrapped = helper(entity);
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
630
|
+
const identifier = wrapped.__identifier;
|
|
631
|
+
const identifiers = Array.isArray(identifier) ? identifier : [identifier];
|
|
632
|
+
for (let i = 0; i < current.primaryKeys.length; i++) {
|
|
633
|
+
const pk = current.primaryKeys[i];
|
|
634
|
+
payload[pk] = identifiers[i] ?? originalChangeSet.payload[pk];
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (!isCreate && Object.keys(payload).length === 0) {
|
|
638
|
+
current = current.tptParent;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
const cs = new ChangeSet(entity, originalChangeSet.type, payload, current);
|
|
642
|
+
if (current === meta) {
|
|
643
|
+
cs.originalEntity = originalChangeSet.originalEntity;
|
|
644
|
+
leafCs = cs;
|
|
645
|
+
} else {
|
|
646
|
+
parentChangeSets.push(cs);
|
|
647
|
+
}
|
|
648
|
+
current = current.tptParent;
|
|
649
|
+
}
|
|
650
|
+
// When only parent properties changed (UPDATE), leaf payload is empty—create a stub anchor
|
|
651
|
+
if (!leafCs && parentChangeSets.length > 0) {
|
|
652
|
+
leafCs = new ChangeSet(entity, originalChangeSet.type, {}, meta);
|
|
653
|
+
leafCs.originalEntity = originalChangeSet.originalEntity;
|
|
654
|
+
}
|
|
655
|
+
// Store the leaf changeset in the main map (entity as key), with parent CSs attached
|
|
656
|
+
if (leafCs) {
|
|
657
|
+
if (parentChangeSets.length > 0) {
|
|
658
|
+
leafCs.tptChangeSets = parentChangeSets;
|
|
659
|
+
}
|
|
660
|
+
this.#changeSets.set(entity, leafCs);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Returns `true` when the change set should be skipped as it will be empty after the extra update.
|
|
665
|
+
*/
|
|
666
|
+
checkUniqueProps(changeSet) {
|
|
667
|
+
if (changeSet.type !== ChangeSetType.UPDATE) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
// when changing a unique nullable property (or a 1:1 relation), we can't do it in a single
|
|
671
|
+
// query as it would cause unique constraint violations
|
|
672
|
+
const uniqueProps = changeSet.meta.uniqueProps.filter(prop => {
|
|
673
|
+
return prop.nullable || changeSet.type !== ChangeSetType.CREATE;
|
|
674
|
+
});
|
|
675
|
+
this.scheduleExtraUpdate(changeSet, uniqueProps);
|
|
676
|
+
return changeSet.type === ChangeSetType.UPDATE && !Utils.hasObjectKeys(changeSet.payload);
|
|
677
|
+
}
|
|
678
|
+
expandUniqueProps(entity) {
|
|
679
|
+
const wrapped = helper(entity);
|
|
680
|
+
if (!wrapped.__meta.hasUniqueProps) {
|
|
681
|
+
return [];
|
|
682
|
+
}
|
|
683
|
+
const simpleUniqueHashes = wrapped.__meta.uniqueProps
|
|
684
|
+
.map(prop => {
|
|
685
|
+
if (entity[prop.name] != null) {
|
|
686
|
+
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
687
|
+
? entity[prop.name]
|
|
688
|
+
: helper(entity[prop.name]).getSerializedPrimaryKey();
|
|
689
|
+
}
|
|
690
|
+
if (wrapped.__originalEntityData?.[prop.name] != null) {
|
|
691
|
+
return Utils.getPrimaryKeyHash(Utils.asArray(wrapped.__originalEntityData[prop.name]));
|
|
692
|
+
}
|
|
693
|
+
return undefined;
|
|
694
|
+
})
|
|
695
|
+
.filter(i => i);
|
|
696
|
+
const compoundUniqueHashes = wrapped.__meta.uniques
|
|
697
|
+
.map(unique => {
|
|
698
|
+
const props = Utils.asArray(unique.properties);
|
|
699
|
+
if (props.every(prop => entity[prop] != null)) {
|
|
700
|
+
return Utils.getPrimaryKeyHash(
|
|
701
|
+
props.map(p => {
|
|
702
|
+
const prop = wrapped.__meta.properties[p];
|
|
703
|
+
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
704
|
+
? entity[prop.name]
|
|
705
|
+
: helper(entity[prop.name]).getSerializedPrimaryKey();
|
|
706
|
+
}),
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
return undefined;
|
|
710
|
+
})
|
|
711
|
+
.filter(i => i);
|
|
712
|
+
return simpleUniqueHashes.concat(compoundUniqueHashes);
|
|
713
|
+
}
|
|
714
|
+
initIdentifier(entity) {
|
|
715
|
+
const wrapped = entity && helper(entity);
|
|
716
|
+
if (!wrapped || wrapped.__identifier || wrapped.hasPrimaryKey()) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const pks = wrapped.__meta.getPrimaryProps();
|
|
720
|
+
const idents = [];
|
|
721
|
+
for (const pk of pks) {
|
|
722
|
+
if (pk.kind === ReferenceKind.SCALAR) {
|
|
723
|
+
idents.push(new EntityIdentifier(entity[pk.name]));
|
|
724
|
+
} else if (entity[pk.name]) {
|
|
725
|
+
this.initIdentifier(entity[pk.name]);
|
|
726
|
+
idents.push(helper(entity[pk.name])?.__identifier);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (pks.length === 1) {
|
|
730
|
+
wrapped.__identifier = idents[0];
|
|
731
|
+
} else {
|
|
732
|
+
wrapped.__identifier = idents;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
processReference(parent, prop, kind, visited, processed, idx) {
|
|
736
|
+
const isToOne = prop.kind === ReferenceKind.MANY_TO_ONE || prop.kind === ReferenceKind.ONE_TO_ONE;
|
|
737
|
+
if (isToOne && Utils.isEntity(kind)) {
|
|
738
|
+
return this.processToOneReference(kind, visited, processed, idx);
|
|
739
|
+
}
|
|
740
|
+
if (Utils.isCollection(kind)) {
|
|
741
|
+
kind
|
|
742
|
+
.getItems(false)
|
|
743
|
+
.filter(item => !item.__helper.__originalEntityData)
|
|
744
|
+
.forEach(item => {
|
|
745
|
+
// propagate schema from parent
|
|
746
|
+
item.__helper.__schema ??= helper(parent).__schema;
|
|
963
747
|
});
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
748
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && kind.isDirty()) {
|
|
749
|
+
this.processToManyReference(kind, visited, processed, parent, prop);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
processToOneReference(kind, visited, processed, idx) {
|
|
754
|
+
if (!kind.__helper.__managed) {
|
|
755
|
+
this.findNewEntities(kind, visited, idx, processed);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
processToManyReference(collection, visited, processed, parent, prop) {
|
|
759
|
+
if (this.isCollectionSelfReferenced(collection, processed)) {
|
|
760
|
+
this.#extraUpdates.add([parent, prop.name, collection, undefined, ChangeSetType.UPDATE]);
|
|
761
|
+
const coll = new Collection(parent);
|
|
762
|
+
coll.property = prop;
|
|
763
|
+
parent[prop.name] = coll;
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
collection
|
|
767
|
+
.getItems(false)
|
|
768
|
+
.filter(item => !item.__helper.__originalEntityData)
|
|
769
|
+
.forEach(item => this.findNewEntities(item, visited, 0, processed));
|
|
770
|
+
}
|
|
771
|
+
async runHooks(type, changeSet, sync = false) {
|
|
772
|
+
const meta = changeSet.meta;
|
|
773
|
+
if (!this.#eventManager.hasListeners(type, meta)) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (!sync) {
|
|
777
|
+
await this.#eventManager.dispatchEvent(type, { entity: changeSet.entity, meta, em: this.#em, changeSet });
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const copy = this.#comparator.prepareEntity(changeSet.entity);
|
|
781
|
+
await this.#eventManager.dispatchEvent(type, { entity: changeSet.entity, meta, em: this.#em, changeSet });
|
|
782
|
+
const current = this.#comparator.prepareEntity(changeSet.entity);
|
|
783
|
+
const diff = this.#comparator.diffEntities(changeSet.meta.class, copy, current);
|
|
784
|
+
Object.assign(changeSet.payload, diff);
|
|
785
|
+
const wrapped = helper(changeSet.entity);
|
|
786
|
+
if (wrapped.__identifier) {
|
|
787
|
+
const idents = Utils.asArray(wrapped.__identifier);
|
|
788
|
+
let i = 0;
|
|
789
|
+
for (const pk of wrapped.__meta.primaryKeys) {
|
|
790
|
+
if (diff[pk]) {
|
|
791
|
+
idents[i].setValue(diff[pk]);
|
|
792
|
+
}
|
|
793
|
+
i++;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
postCommitCleanup() {
|
|
798
|
+
for (const cs of this.#changeSets.values()) {
|
|
799
|
+
const wrapped = helper(cs.entity);
|
|
800
|
+
wrapped.__processing = false;
|
|
801
|
+
delete wrapped.__pk;
|
|
802
|
+
}
|
|
803
|
+
this.#persistStack.clear();
|
|
804
|
+
this.#removeStack.clear();
|
|
805
|
+
this.#orphanRemoveStack.clear();
|
|
806
|
+
this.#changeSets.clear();
|
|
807
|
+
this.#collectionUpdates.clear();
|
|
808
|
+
this.#extraUpdates.clear();
|
|
809
|
+
this.#queuedActions.clear();
|
|
810
|
+
this.#working = false;
|
|
811
|
+
}
|
|
812
|
+
cascade(entity, type, visited = new Set(), options = {}) {
|
|
813
|
+
if (visited.has(entity)) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
visited.add(entity);
|
|
817
|
+
switch (type) {
|
|
818
|
+
case Cascade.PERSIST:
|
|
819
|
+
this.persist(entity, visited, options);
|
|
820
|
+
break;
|
|
821
|
+
case Cascade.MERGE:
|
|
822
|
+
this.merge(entity, visited);
|
|
823
|
+
break;
|
|
824
|
+
case Cascade.REMOVE:
|
|
825
|
+
this.remove(entity, visited, options);
|
|
826
|
+
break;
|
|
827
|
+
case Cascade.SCHEDULE_ORPHAN_REMOVAL:
|
|
828
|
+
this.scheduleOrphanRemoval(entity, visited);
|
|
829
|
+
break;
|
|
830
|
+
case Cascade.CANCEL_ORPHAN_REMOVAL:
|
|
831
|
+
this.cancelOrphanRemoval(entity, visited);
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
for (const prop of helper(entity).__meta.relations) {
|
|
835
|
+
this.cascadeReference(entity, prop, type, visited, options);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
cascadeReference(entity, prop, type, visited, options) {
|
|
839
|
+
this.fixMissingReference(entity, prop);
|
|
840
|
+
if (!this.shouldCascade(prop, type)) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const kind = Reference.unwrapReference(entity[prop.name]);
|
|
844
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isEntity(kind)) {
|
|
845
|
+
return this.cascade(kind, type, visited, options);
|
|
846
|
+
}
|
|
847
|
+
const collection = kind;
|
|
848
|
+
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind) && collection) {
|
|
849
|
+
for (const item of collection.getItems(false)) {
|
|
850
|
+
this.cascade(item, type, visited, options);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
isCollectionSelfReferenced(collection, processed) {
|
|
855
|
+
const filtered = collection.getItems(false).filter(item => !helper(item).__originalEntityData);
|
|
856
|
+
return filtered.some(items => processed.has(items));
|
|
857
|
+
}
|
|
858
|
+
shouldCascade(prop, type) {
|
|
859
|
+
if (
|
|
860
|
+
[Cascade.REMOVE, Cascade.SCHEDULE_ORPHAN_REMOVAL, Cascade.CANCEL_ORPHAN_REMOVAL, Cascade.ALL].includes(type) &&
|
|
861
|
+
prop.orphanRemoval
|
|
862
|
+
) {
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
// ignore user settings for merge, it is kept only for back compatibility, this should have never been configurable
|
|
866
|
+
if (type === Cascade.MERGE) {
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
return prop.cascade && (prop.cascade.includes(type) || prop.cascade.includes(Cascade.ALL));
|
|
870
|
+
}
|
|
871
|
+
async lockPessimistic(entity, options) {
|
|
872
|
+
if (!this.#em.isInTransaction()) {
|
|
873
|
+
throw ValidationError.transactionRequired();
|
|
874
|
+
}
|
|
875
|
+
await this.#em.getDriver().lockPessimistic(entity, { ctx: this.#em.getTransactionContext(), ...options });
|
|
876
|
+
}
|
|
877
|
+
async lockOptimistic(entity, meta, version) {
|
|
878
|
+
if (!meta.versionProperty) {
|
|
879
|
+
throw OptimisticLockError.notVersioned(meta);
|
|
880
|
+
}
|
|
881
|
+
if (typeof version === 'undefined') {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const wrapped = helper(entity);
|
|
885
|
+
if (!wrapped.__initialized) {
|
|
886
|
+
await wrapped.init();
|
|
887
|
+
}
|
|
888
|
+
const previousVersion = entity[meta.versionProperty];
|
|
889
|
+
if (previousVersion !== version) {
|
|
890
|
+
throw OptimisticLockError.lockFailedVersionMismatch(entity, version, previousVersion);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
fixMissingReference(entity, prop) {
|
|
894
|
+
const reference = entity[prop.name];
|
|
895
|
+
const target = Reference.unwrapReference(reference);
|
|
896
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && target && !prop.mapToPk) {
|
|
897
|
+
if (!Utils.isEntity(target)) {
|
|
898
|
+
entity[prop.name] = this.#em.getReference(prop.targetMeta.class, target, {
|
|
899
|
+
wrapped: !!prop.ref,
|
|
1092
900
|
});
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
filterCollectionUpdates() {
|
|
1098
|
-
for (const coll of this.#collectionUpdates) {
|
|
1099
|
-
let skip = true;
|
|
1100
|
-
if (coll.property.owner || coll.getItems(false).filter(item => !item.__helper.__initialized).length > 0) {
|
|
1101
|
-
if (this.#platform.usesPivotTable()) {
|
|
1102
|
-
skip = false;
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
else if (coll.property.kind === ReferenceKind.ONE_TO_MANY && coll.getSnapshot() === undefined) {
|
|
1106
|
-
skip = false;
|
|
1107
|
-
}
|
|
1108
|
-
else if (coll.property.kind === ReferenceKind.MANY_TO_MANY && !coll.property.owner) {
|
|
1109
|
-
skip = false;
|
|
1110
|
-
}
|
|
1111
|
-
if (skip) {
|
|
1112
|
-
this.#collectionUpdates.delete(coll);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
/**
|
|
1117
|
-
* Orders change sets so FK constrains are maintained, ensures stable order (needed for node < 11)
|
|
1118
|
-
*/
|
|
1119
|
-
getChangeSetGroups() {
|
|
1120
|
-
const groups = {
|
|
1121
|
-
[ChangeSetType.CREATE]: new Map(),
|
|
1122
|
-
[ChangeSetType.UPDATE]: new Map(),
|
|
1123
|
-
[ChangeSetType.DELETE]: new Map(),
|
|
1124
|
-
[ChangeSetType.UPDATE_EARLY]: new Map(),
|
|
1125
|
-
[ChangeSetType.DELETE_EARLY]: new Map(),
|
|
1126
|
-
};
|
|
1127
|
-
const addToGroup = (cs) => {
|
|
1128
|
-
// Skip stub TPT changesets with empty payload (e.g. leaf with no own-property changes on UPDATE)
|
|
1129
|
-
if ((cs.type === ChangeSetType.UPDATE || cs.type === ChangeSetType.UPDATE_EARLY) &&
|
|
1130
|
-
!Utils.hasObjectKeys(cs.payload)) {
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
const group = groups[cs.type];
|
|
1134
|
-
const groupKey = cs.meta.inheritanceType === 'tpt' ? cs.meta : cs.rootMeta;
|
|
1135
|
-
const classGroup = group.get(groupKey) ?? [];
|
|
1136
|
-
classGroup.push(cs);
|
|
1137
|
-
if (!group.has(groupKey)) {
|
|
1138
|
-
group.set(groupKey, classGroup);
|
|
1139
|
-
}
|
|
1140
|
-
};
|
|
1141
|
-
for (const cs of this.#changeSets.values()) {
|
|
1142
|
-
addToGroup(cs);
|
|
1143
|
-
for (const parentCs of cs.tptChangeSets ?? []) {
|
|
1144
|
-
addToGroup(parentCs);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
return groups;
|
|
1148
|
-
}
|
|
1149
|
-
getCommitOrder() {
|
|
1150
|
-
const calc = new CommitOrderCalculator();
|
|
1151
|
-
const set = new Set();
|
|
1152
|
-
this.#changeSets.forEach(cs => {
|
|
1153
|
-
if (cs.meta.inheritanceType === 'tpt') {
|
|
1154
|
-
set.add(cs.meta);
|
|
1155
|
-
for (const parentCs of cs.tptChangeSets ?? []) {
|
|
1156
|
-
set.add(parentCs.meta);
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
else {
|
|
1160
|
-
set.add(cs.rootMeta);
|
|
1161
|
-
}
|
|
901
|
+
} else if (!helper(target).__initialized && !helper(target).__em) {
|
|
902
|
+
const pk = helper(target).getPrimaryKey();
|
|
903
|
+
entity[prop.name] = this.#em.getReference(prop.targetMeta.class, pk, {
|
|
904
|
+
wrapped: !!prop.ref,
|
|
1162
905
|
});
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
// perf: set the `Collection._property` to skip the getter, as it can be slow when there are a lot of relations
|
|
909
|
+
if (Utils.isCollection(target)) {
|
|
910
|
+
target.property = prop;
|
|
911
|
+
}
|
|
912
|
+
const isCollection = [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind);
|
|
913
|
+
if (isCollection && Array.isArray(target)) {
|
|
914
|
+
const collection = new Collection(entity);
|
|
915
|
+
collection.property = prop;
|
|
916
|
+
entity[prop.name] = collection;
|
|
917
|
+
collection.set(target);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
async persistToDatabase(groups, ctx) {
|
|
921
|
+
if (ctx) {
|
|
922
|
+
this.#em.setTransactionContext(ctx);
|
|
923
|
+
}
|
|
924
|
+
const commitOrder = this.getCommitOrder();
|
|
925
|
+
const commitOrderReversed = [...commitOrder].reverse();
|
|
926
|
+
// early delete - when we recreate entity in the same UoW, we need to issue those delete queries before inserts
|
|
927
|
+
for (const meta of commitOrderReversed) {
|
|
928
|
+
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE_EARLY].get(meta) ?? [], ctx);
|
|
929
|
+
}
|
|
930
|
+
// early update - when we recreate entity in the same UoW, we need to issue those delete queries before inserts
|
|
931
|
+
for (const meta of commitOrder) {
|
|
932
|
+
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE_EARLY].get(meta) ?? [], ctx);
|
|
933
|
+
}
|
|
934
|
+
// extra updates
|
|
935
|
+
await this.commitExtraUpdates(ChangeSetType.UPDATE_EARLY, ctx);
|
|
936
|
+
// create
|
|
937
|
+
for (const meta of commitOrder) {
|
|
938
|
+
await this.commitCreateChangeSets(groups[ChangeSetType.CREATE].get(meta) ?? [], ctx);
|
|
939
|
+
}
|
|
940
|
+
// update
|
|
941
|
+
for (const meta of commitOrder) {
|
|
942
|
+
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE].get(meta) ?? [], ctx);
|
|
943
|
+
}
|
|
944
|
+
// extra updates
|
|
945
|
+
await this.commitExtraUpdates(ChangeSetType.UPDATE, ctx);
|
|
946
|
+
// collection updates
|
|
947
|
+
await this.commitCollectionUpdates(ctx);
|
|
948
|
+
// delete - entity deletions need to be in reverse commit order
|
|
949
|
+
for (const meta of commitOrderReversed) {
|
|
950
|
+
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE].get(meta) ?? [], ctx);
|
|
951
|
+
}
|
|
952
|
+
// take snapshots of all persisted collections
|
|
953
|
+
const visited = new Set();
|
|
954
|
+
for (const changeSet of this.#changeSets.values()) {
|
|
955
|
+
this.takeCollectionSnapshots(changeSet.entity, visited);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
async commitCreateChangeSets(changeSets, ctx) {
|
|
959
|
+
if (changeSets.length === 0) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const props = changeSets[0].meta.root.relations.filter(prop => {
|
|
963
|
+
return (
|
|
964
|
+
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner) ||
|
|
965
|
+
prop.kind === ReferenceKind.MANY_TO_ONE ||
|
|
966
|
+
(prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !this.#platform.usesPivotTable())
|
|
967
|
+
);
|
|
968
|
+
});
|
|
969
|
+
for (const changeSet of changeSets) {
|
|
970
|
+
this.findExtraUpdates(changeSet, props);
|
|
971
|
+
await this.runHooks(EventType.beforeCreate, changeSet, true);
|
|
972
|
+
}
|
|
973
|
+
await this.#changeSetPersister.executeInserts(changeSets, { ctx });
|
|
974
|
+
for (const changeSet of changeSets) {
|
|
975
|
+
this.register(changeSet.entity, changeSet.payload, { refresh: true });
|
|
976
|
+
await this.runHooks(EventType.afterCreate, changeSet);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
findExtraUpdates(changeSet, props) {
|
|
980
|
+
for (const prop of props) {
|
|
981
|
+
const ref = changeSet.entity[prop.name];
|
|
982
|
+
if (!ref || prop.deferMode === DeferMode.INITIALLY_DEFERRED) {
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
if (Utils.isCollection(ref)) {
|
|
986
|
+
ref.getItems(false).some(item => {
|
|
987
|
+
const cs = this.#changeSets.get(Reference.unwrapReference(item));
|
|
988
|
+
const isScheduledForInsert = cs?.type === ChangeSetType.CREATE && !cs.persisted;
|
|
989
|
+
if (isScheduledForInsert) {
|
|
990
|
+
this.scheduleExtraUpdate(changeSet, [prop]);
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
return false;
|
|
1207
994
|
});
|
|
1208
|
-
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const refEntity = Reference.unwrapReference(ref);
|
|
998
|
+
// For mapToPk properties, the value is a primitive (string/array), not an entity
|
|
999
|
+
if (!Utils.isEntity(refEntity)) {
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
// For TPT entities, check if the ROOT table's changeset has been persisted
|
|
1003
|
+
// (since the FK is to the root table, not the concrete entity's table)
|
|
1004
|
+
let cs = this.#changeSets.get(refEntity);
|
|
1005
|
+
if (cs?.tptChangeSets?.length) {
|
|
1006
|
+
// Root table changeset is the last one (ordered immediate parent → root)
|
|
1007
|
+
cs = cs.tptChangeSets[cs.tptChangeSets.length - 1];
|
|
1008
|
+
}
|
|
1009
|
+
const isScheduledForInsert = cs?.type === ChangeSetType.CREATE && !cs.persisted;
|
|
1010
|
+
if (isScheduledForInsert) {
|
|
1011
|
+
this.scheduleExtraUpdate(changeSet, [prop]);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
findEarlyUpdates(changeSet, inserts = []) {
|
|
1016
|
+
const props = changeSet.meta.uniqueProps;
|
|
1017
|
+
for (const prop of props) {
|
|
1018
|
+
const insert = inserts.find(c => Utils.equals(c.payload[prop.name], changeSet.originalEntity[prop.name]));
|
|
1019
|
+
const propEmpty = changeSet.payload[prop.name] === null || changeSet.payload[prop.name] === undefined;
|
|
1020
|
+
if (
|
|
1021
|
+
prop.name in changeSet.payload &&
|
|
1022
|
+
insert &&
|
|
1023
|
+
// We only want to update early if the unique property on the changeset is going to be empty, so that
|
|
1024
|
+
// the previous unique value can be set on a different entity without constraint issues
|
|
1025
|
+
propEmpty
|
|
1026
|
+
) {
|
|
1027
|
+
changeSet.type = ChangeSetType.UPDATE_EARLY;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
async commitUpdateChangeSets(changeSets, ctx, batched = true) {
|
|
1032
|
+
if (changeSets.length === 0) {
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
for (const changeSet of changeSets) {
|
|
1036
|
+
await this.runHooks(EventType.beforeUpdate, changeSet, true);
|
|
1037
|
+
}
|
|
1038
|
+
await this.#changeSetPersister.executeUpdates(changeSets, batched, { ctx });
|
|
1039
|
+
for (const changeSet of changeSets) {
|
|
1040
|
+
const wrapped = helper(changeSet.entity);
|
|
1041
|
+
wrapped.__originalEntityData = this.#comparator.prepareEntity(changeSet.entity);
|
|
1042
|
+
if (!wrapped.__initialized) {
|
|
1043
|
+
for (const prop of changeSet.meta.relations) {
|
|
1044
|
+
if (
|
|
1045
|
+
[ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
|
|
1046
|
+
changeSet.entity[prop.name] == null
|
|
1047
|
+
) {
|
|
1048
|
+
changeSet.entity[prop.name] = Collection.create(
|
|
1049
|
+
changeSet.entity,
|
|
1050
|
+
prop.name,
|
|
1051
|
+
undefined,
|
|
1052
|
+
wrapped.isInitialized(),
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
wrapped.__initialized = true;
|
|
1057
|
+
}
|
|
1058
|
+
await this.runHooks(EventType.afterUpdate, changeSet);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
async commitDeleteChangeSets(changeSets, ctx) {
|
|
1062
|
+
if (changeSets.length === 0) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
for (const changeSet of changeSets) {
|
|
1066
|
+
await this.runHooks(EventType.beforeDelete, changeSet, true);
|
|
1067
|
+
}
|
|
1068
|
+
await this.#changeSetPersister.executeDeletes(changeSets, { ctx });
|
|
1069
|
+
for (const changeSet of changeSets) {
|
|
1070
|
+
this.unsetIdentity(changeSet.entity);
|
|
1071
|
+
await this.runHooks(EventType.afterDelete, changeSet);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
async commitExtraUpdates(type, ctx) {
|
|
1075
|
+
const extraUpdates = [];
|
|
1076
|
+
for (const extraUpdate of this.#extraUpdates) {
|
|
1077
|
+
if (extraUpdate[4] !== type) {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (Array.isArray(extraUpdate[1])) {
|
|
1081
|
+
extraUpdate[1].forEach((p, i) => (extraUpdate[0][p] = extraUpdate[2][i]));
|
|
1082
|
+
} else {
|
|
1083
|
+
extraUpdate[0][extraUpdate[1]] = extraUpdate[2];
|
|
1084
|
+
}
|
|
1085
|
+
const changeSet = this.#changeSetComputer.computeChangeSet(extraUpdate[0]);
|
|
1086
|
+
if (changeSet) {
|
|
1087
|
+
extraUpdates.push([changeSet, extraUpdate[3]]);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
await this.commitUpdateChangeSets(
|
|
1091
|
+
extraUpdates.map(u => u[0]),
|
|
1092
|
+
ctx,
|
|
1093
|
+
false,
|
|
1094
|
+
);
|
|
1095
|
+
// propagate the new values to the original changeset
|
|
1096
|
+
for (const extraUpdate of extraUpdates) {
|
|
1097
|
+
if (extraUpdate[1]) {
|
|
1098
|
+
Object.assign(extraUpdate[1].payload, extraUpdate[0].payload);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
async commitCollectionUpdates(ctx) {
|
|
1103
|
+
this.filterCollectionUpdates();
|
|
1104
|
+
const loggerContext = Utils.merge(
|
|
1105
|
+
{ id: this.#em._id },
|
|
1106
|
+
this.#em.getLoggerContext({ disableContextResolution: true }),
|
|
1107
|
+
);
|
|
1108
|
+
await this.#em.getDriver().syncCollections(this.#collectionUpdates, {
|
|
1109
|
+
ctx,
|
|
1110
|
+
schema: this.#em.schema,
|
|
1111
|
+
loggerContext,
|
|
1112
|
+
});
|
|
1113
|
+
for (const coll of this.#collectionUpdates) {
|
|
1114
|
+
coll.takeSnapshot();
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
filterCollectionUpdates() {
|
|
1118
|
+
for (const coll of this.#collectionUpdates) {
|
|
1119
|
+
let skip = true;
|
|
1120
|
+
if (coll.property.owner || coll.getItems(false).filter(item => !item.__helper.__initialized).length > 0) {
|
|
1121
|
+
if (this.#platform.usesPivotTable()) {
|
|
1122
|
+
skip = false;
|
|
1123
|
+
}
|
|
1124
|
+
} else if (coll.property.kind === ReferenceKind.ONE_TO_MANY && coll.getSnapshot() === undefined) {
|
|
1125
|
+
skip = false;
|
|
1126
|
+
} else if (coll.property.kind === ReferenceKind.MANY_TO_MANY && !coll.property.owner) {
|
|
1127
|
+
skip = false;
|
|
1128
|
+
}
|
|
1129
|
+
if (skip) {
|
|
1130
|
+
this.#collectionUpdates.delete(coll);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Orders change sets so FK constrains are maintained, ensures stable order (needed for node < 11)
|
|
1136
|
+
*/
|
|
1137
|
+
getChangeSetGroups() {
|
|
1138
|
+
const groups = {
|
|
1139
|
+
[ChangeSetType.CREATE]: new Map(),
|
|
1140
|
+
[ChangeSetType.UPDATE]: new Map(),
|
|
1141
|
+
[ChangeSetType.DELETE]: new Map(),
|
|
1142
|
+
[ChangeSetType.UPDATE_EARLY]: new Map(),
|
|
1143
|
+
[ChangeSetType.DELETE_EARLY]: new Map(),
|
|
1144
|
+
};
|
|
1145
|
+
const addToGroup = cs => {
|
|
1146
|
+
// Skip stub TPT changesets with empty payload (e.g. leaf with no own-property changes on UPDATE)
|
|
1147
|
+
if (
|
|
1148
|
+
(cs.type === ChangeSetType.UPDATE || cs.type === ChangeSetType.UPDATE_EARLY) &&
|
|
1149
|
+
!Utils.hasObjectKeys(cs.payload)
|
|
1150
|
+
) {
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
const group = groups[cs.type];
|
|
1154
|
+
const groupKey = cs.meta.inheritanceType === 'tpt' ? cs.meta : cs.rootMeta;
|
|
1155
|
+
const classGroup = group.get(groupKey) ?? [];
|
|
1156
|
+
classGroup.push(cs);
|
|
1157
|
+
if (!group.has(groupKey)) {
|
|
1158
|
+
group.set(groupKey, classGroup);
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
for (const cs of this.#changeSets.values()) {
|
|
1162
|
+
addToGroup(cs);
|
|
1163
|
+
for (const parentCs of cs.tptChangeSets ?? []) {
|
|
1164
|
+
addToGroup(parentCs);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return groups;
|
|
1168
|
+
}
|
|
1169
|
+
getCommitOrder() {
|
|
1170
|
+
const calc = new CommitOrderCalculator();
|
|
1171
|
+
const set = new Set();
|
|
1172
|
+
this.#changeSets.forEach(cs => {
|
|
1173
|
+
if (cs.meta.inheritanceType === 'tpt') {
|
|
1174
|
+
set.add(cs.meta);
|
|
1175
|
+
for (const parentCs of cs.tptChangeSets ?? []) {
|
|
1176
|
+
set.add(parentCs.meta);
|
|
1177
|
+
}
|
|
1178
|
+
} else {
|
|
1179
|
+
set.add(cs.rootMeta);
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
set.forEach(meta => calc.addNode(meta._id));
|
|
1183
|
+
for (const meta of set) {
|
|
1184
|
+
for (const prop of meta.relations) {
|
|
1185
|
+
if (prop.polymorphTargets) {
|
|
1186
|
+
for (const targetMeta of prop.polymorphTargets) {
|
|
1187
|
+
calc.discoverProperty({ ...prop, targetMeta }, meta._id);
|
|
1188
|
+
}
|
|
1189
|
+
} else {
|
|
1190
|
+
calc.discoverProperty(prop, meta._id);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
// For TPT, parent table must be inserted BEFORE child tables
|
|
1194
|
+
if (meta.inheritanceType === 'tpt' && meta.tptParent && set.has(meta.tptParent)) {
|
|
1195
|
+
calc.addDependency(meta.tptParent._id, meta._id, 1);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return calc.sort().map(id => this.#metadata.getById(id));
|
|
1199
|
+
}
|
|
1200
|
+
resetTransaction(oldTx) {
|
|
1201
|
+
if (oldTx) {
|
|
1202
|
+
this.#em.setTransactionContext(oldTx);
|
|
1203
|
+
} else {
|
|
1204
|
+
this.#em.resetTransactionContext();
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Takes snapshots of all processed collections
|
|
1209
|
+
*/
|
|
1210
|
+
takeCollectionSnapshots(entity, visited) {
|
|
1211
|
+
if (visited.has(entity)) {
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
visited.add(entity);
|
|
1215
|
+
helper(entity)?.__meta.relations.forEach(prop => {
|
|
1216
|
+
const value = entity[prop.name];
|
|
1217
|
+
if (Utils.isCollection(value)) {
|
|
1218
|
+
value.takeSnapshot();
|
|
1219
|
+
}
|
|
1220
|
+
// cascade to m:1 relations as we need to snapshot the 1:m inverse side (for `removeAll()` with orphan removal)
|
|
1221
|
+
if (prop.kind === ReferenceKind.MANY_TO_ONE && value) {
|
|
1222
|
+
this.takeCollectionSnapshots(Reference.unwrapReference(value), visited);
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1209
1226
|
}
|