@mikro-orm/core 7.0.0-dev.23 → 7.0.0-dev.231
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 +91 -59
- package/EntityManager.js +303 -251
- package/MikroORM.d.ts +44 -35
- package/MikroORM.js +109 -143
- package/README.md +2 -0
- package/cache/FileCacheAdapter.d.ts +1 -1
- package/cache/FileCacheAdapter.js +17 -8
- package/cache/GeneratedCacheAdapter.d.ts +0 -1
- package/cache/GeneratedCacheAdapter.js +0 -2
- package/cache/index.d.ts +0 -1
- package/cache/index.js +0 -1
- package/connections/Connection.d.ts +12 -5
- package/connections/Connection.js +21 -12
- package/drivers/DatabaseDriver.d.ts +25 -16
- package/drivers/DatabaseDriver.js +118 -35
- package/drivers/IDatabaseDriver.d.ts +42 -19
- package/entity/BaseEntity.d.ts +61 -2
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +101 -29
- package/entity/Collection.js +436 -104
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +26 -18
- package/entity/EntityFactory.d.ts +7 -1
- package/entity/EntityFactory.js +83 -54
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +48 -15
- package/entity/EntityLoader.d.ts +7 -6
- package/entity/EntityLoader.js +221 -93
- package/entity/EntityRepository.d.ts +27 -7
- package/entity/EntityRepository.js +8 -2
- package/entity/PolymorphicRef.d.ts +12 -0
- package/entity/PolymorphicRef.js +18 -0
- package/entity/Reference.d.ts +1 -5
- package/entity/Reference.js +21 -12
- package/entity/WrappedEntity.d.ts +0 -5
- package/entity/WrappedEntity.js +2 -7
- package/entity/defineEntity.d.ts +380 -310
- package/entity/defineEntity.js +124 -273
- package/entity/index.d.ts +2 -2
- package/entity/index.js +2 -2
- package/entity/utils.js +1 -1
- package/entity/validators.d.ts +11 -0
- package/entity/validators.js +65 -0
- package/enums.d.ts +8 -6
- package/enums.js +2 -1
- package/errors.d.ts +20 -10
- package/errors.js +55 -23
- package/events/EventManager.d.ts +2 -1
- package/events/EventManager.js +19 -11
- package/hydration/Hydrator.js +1 -2
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +87 -35
- package/index.d.ts +2 -2
- package/index.js +1 -2
- package/logging/DefaultLogger.d.ts +1 -1
- package/logging/DefaultLogger.js +1 -0
- package/logging/SimpleLogger.d.ts +1 -1
- package/logging/colors.d.ts +1 -1
- package/logging/colors.js +7 -6
- package/logging/index.d.ts +1 -0
- package/logging/index.js +1 -0
- package/logging/inspect.d.ts +2 -0
- package/logging/inspect.js +11 -0
- package/metadata/EntitySchema.d.ts +47 -23
- package/metadata/EntitySchema.js +92 -33
- package/metadata/MetadataDiscovery.d.ts +64 -9
- package/metadata/MetadataDiscovery.js +778 -325
- package/metadata/MetadataProvider.d.ts +11 -2
- package/metadata/MetadataProvider.js +46 -2
- package/metadata/MetadataStorage.d.ts +13 -11
- package/metadata/MetadataStorage.js +70 -37
- package/metadata/MetadataValidator.d.ts +32 -9
- package/metadata/MetadataValidator.js +196 -41
- package/metadata/discover-entities.d.ts +5 -0
- package/metadata/discover-entities.js +40 -0
- package/metadata/index.d.ts +1 -1
- package/metadata/index.js +1 -1
- package/metadata/types.d.ts +526 -0
- package/metadata/types.js +1 -0
- package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
- package/naming-strategy/AbstractNamingStrategy.js +20 -2
- package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
- package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
- package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
- package/naming-strategy/MongoNamingStrategy.js +6 -6
- package/naming-strategy/NamingStrategy.d.ts +28 -4
- package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
- package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
- package/not-supported.d.ts +2 -0
- package/not-supported.js +4 -0
- package/package.json +19 -11
- package/platforms/ExceptionConverter.js +1 -1
- package/platforms/Platform.d.ts +7 -14
- package/platforms/Platform.js +20 -43
- package/serialization/EntitySerializer.d.ts +5 -0
- package/serialization/EntitySerializer.js +47 -27
- package/serialization/EntityTransformer.js +28 -18
- package/serialization/SerializationContext.d.ts +6 -6
- package/serialization/SerializationContext.js +3 -3
- package/types/ArrayType.d.ts +1 -1
- package/types/ArrayType.js +2 -3
- package/types/BigIntType.d.ts +8 -6
- package/types/BigIntType.js +1 -1
- package/types/BlobType.d.ts +0 -1
- package/types/BlobType.js +0 -3
- package/types/BooleanType.d.ts +1 -0
- package/types/BooleanType.js +3 -0
- package/types/DecimalType.d.ts +6 -4
- package/types/DecimalType.js +2 -2
- package/types/DoubleType.js +1 -1
- package/types/EnumArrayType.js +1 -2
- package/types/JsonType.d.ts +1 -1
- package/types/JsonType.js +7 -2
- package/types/TinyIntType.js +1 -1
- package/types/Type.d.ts +2 -4
- package/types/Type.js +3 -3
- package/types/Uint8ArrayType.d.ts +0 -1
- package/types/Uint8ArrayType.js +1 -4
- package/types/index.d.ts +1 -1
- package/typings.d.ts +381 -171
- package/typings.js +97 -44
- package/unit-of-work/ChangeSet.d.ts +4 -6
- package/unit-of-work/ChangeSet.js +4 -5
- package/unit-of-work/ChangeSetComputer.d.ts +1 -3
- package/unit-of-work/ChangeSetComputer.js +35 -14
- package/unit-of-work/ChangeSetPersister.d.ts +7 -3
- package/unit-of-work/ChangeSetPersister.js +83 -25
- package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
- package/unit-of-work/CommitOrderCalculator.js +13 -13
- package/unit-of-work/IdentityMap.d.ts +12 -0
- package/unit-of-work/IdentityMap.js +39 -1
- package/unit-of-work/UnitOfWork.d.ts +27 -3
- package/unit-of-work/UnitOfWork.js +258 -92
- package/utils/AbstractSchemaGenerator.d.ts +5 -5
- package/utils/AbstractSchemaGenerator.js +28 -17
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +795 -209
- package/utils/Configuration.js +150 -192
- package/utils/ConfigurationLoader.d.ts +1 -54
- package/utils/ConfigurationLoader.js +1 -352
- package/utils/Cursor.d.ts +0 -3
- package/utils/Cursor.js +24 -11
- package/utils/DataloaderUtils.d.ts +10 -5
- package/utils/DataloaderUtils.js +29 -12
- package/utils/EntityComparator.d.ts +16 -9
- package/utils/EntityComparator.js +158 -58
- package/utils/QueryHelper.d.ts +18 -6
- package/utils/QueryHelper.js +76 -23
- package/utils/RawQueryFragment.d.ts +28 -34
- package/utils/RawQueryFragment.js +35 -71
- package/utils/RequestContext.js +2 -2
- package/utils/TransactionContext.js +2 -2
- package/utils/TransactionManager.js +28 -4
- package/utils/Utils.d.ts +14 -127
- package/utils/Utils.js +85 -397
- package/utils/clone.js +8 -23
- package/utils/env-vars.d.ts +7 -0
- package/utils/env-vars.js +97 -0
- package/utils/fs-utils.d.ts +33 -0
- package/utils/fs-utils.js +192 -0
- package/utils/index.d.ts +1 -1
- package/utils/index.js +1 -1
- package/utils/upsert-utils.d.ts +9 -4
- package/utils/upsert-utils.js +46 -3
- package/decorators/Check.d.ts +0 -3
- package/decorators/Check.js +0 -13
- package/decorators/CreateRequestContext.d.ts +0 -3
- package/decorators/CreateRequestContext.js +0 -32
- package/decorators/Embeddable.d.ts +0 -8
- package/decorators/Embeddable.js +0 -11
- package/decorators/Embedded.d.ts +0 -12
- package/decorators/Embedded.js +0 -18
- package/decorators/Entity.d.ts +0 -33
- package/decorators/Entity.js +0 -12
- package/decorators/Enum.d.ts +0 -9
- package/decorators/Enum.js +0 -16
- package/decorators/Filter.d.ts +0 -2
- package/decorators/Filter.js +0 -8
- package/decorators/Formula.d.ts +0 -4
- package/decorators/Formula.js +0 -15
- package/decorators/Indexed.d.ts +0 -19
- package/decorators/Indexed.js +0 -20
- package/decorators/ManyToMany.d.ts +0 -42
- package/decorators/ManyToMany.js +0 -14
- package/decorators/ManyToOne.d.ts +0 -34
- package/decorators/ManyToOne.js +0 -14
- package/decorators/OneToMany.d.ts +0 -28
- package/decorators/OneToMany.js +0 -17
- package/decorators/OneToOne.d.ts +0 -28
- package/decorators/OneToOne.js +0 -7
- package/decorators/PrimaryKey.d.ts +0 -8
- package/decorators/PrimaryKey.js +0 -20
- package/decorators/Property.d.ts +0 -250
- package/decorators/Property.js +0 -32
- package/decorators/Transactional.d.ts +0 -14
- package/decorators/Transactional.js +0 -28
- package/decorators/hooks.d.ts +0 -16
- package/decorators/hooks.js +0 -47
- package/decorators/index.d.ts +0 -17
- package/decorators/index.js +0 -17
- package/entity/ArrayCollection.d.ts +0 -118
- package/entity/ArrayCollection.js +0 -407
- package/entity/EntityValidator.d.ts +0 -19
- package/entity/EntityValidator.js +0 -150
- package/metadata/ReflectMetadataProvider.d.ts +0 -8
- package/metadata/ReflectMetadataProvider.js +0 -44
- package/utils/resolveContextProvider.d.ts +0 -10
- package/utils/resolveContextProvider.js +0 -28
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { basename, extname } from 'node:path';
|
|
2
|
-
import { glob } from 'tinyglobby';
|
|
3
1
|
import { EntityMetadata, } from '../typings.js';
|
|
4
|
-
import { Utils } from '../utils/Utils.js';
|
|
2
|
+
import { compareArrays, Utils } from '../utils/Utils.js';
|
|
3
|
+
import { QueryHelper } from '../utils/QueryHelper.js';
|
|
5
4
|
import { MetadataValidator } from './MetadataValidator.js';
|
|
5
|
+
import { MetadataProvider } from './MetadataProvider.js';
|
|
6
6
|
import { MetadataStorage } from './MetadataStorage.js';
|
|
7
7
|
import { EntitySchema } from './EntitySchema.js';
|
|
8
8
|
import { Cascade, ReferenceKind } from '../enums.js';
|
|
9
9
|
import { MetadataError } from '../errors.js';
|
|
10
|
-
import {
|
|
10
|
+
import { t, Type } from '../types/index.js';
|
|
11
11
|
import { colors } from '../logging/colors.js';
|
|
12
|
-
import { raw,
|
|
12
|
+
import { raw, Raw } from '../utils/RawQueryFragment.js';
|
|
13
|
+
import { BaseEntity } from '../entity/BaseEntity.js';
|
|
13
14
|
export class MetadataDiscovery {
|
|
14
15
|
metadata;
|
|
15
16
|
platform;
|
|
16
17
|
config;
|
|
17
18
|
namingStrategy;
|
|
18
19
|
metadataProvider;
|
|
19
|
-
cache;
|
|
20
20
|
logger;
|
|
21
21
|
schemaHelper;
|
|
22
22
|
validator = new MetadataValidator();
|
|
@@ -27,13 +27,14 @@ export class MetadataDiscovery {
|
|
|
27
27
|
this.config = config;
|
|
28
28
|
this.namingStrategy = this.config.getNamingStrategy();
|
|
29
29
|
this.metadataProvider = this.config.getMetadataProvider();
|
|
30
|
-
this.cache = this.config.getMetadataCacheAdapter();
|
|
31
30
|
this.logger = this.config.getLogger();
|
|
32
31
|
this.schemaHelper = this.platform.getSchemaHelper();
|
|
33
32
|
}
|
|
34
33
|
async discover(preferTs = true) {
|
|
34
|
+
this.discovered.length = 0;
|
|
35
35
|
const startTime = Date.now();
|
|
36
|
-
this.
|
|
36
|
+
const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
|
|
37
|
+
this.logger.log('discovery', `ORM entity discovery started${suffix}`);
|
|
37
38
|
await this.findEntities(preferTs);
|
|
38
39
|
for (const meta of this.discovered) {
|
|
39
40
|
/* v8 ignore next */
|
|
@@ -47,10 +48,13 @@ export class MetadataDiscovery {
|
|
|
47
48
|
await this.config.get('discovery').afterDiscovered?.(storage, this.platform);
|
|
48
49
|
return storage;
|
|
49
50
|
}
|
|
50
|
-
discoverSync(
|
|
51
|
+
discoverSync() {
|
|
52
|
+
this.discovered.length = 0;
|
|
51
53
|
const startTime = Date.now();
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
54
|
+
const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
|
|
55
|
+
this.logger.log('discovery', `ORM entity discovery started${suffix} in sync mode`);
|
|
56
|
+
const refs = this.config.get('entities');
|
|
57
|
+
this.discoverReferences(refs);
|
|
54
58
|
for (const meta of this.discovered) {
|
|
55
59
|
/* v8 ignore next */
|
|
56
60
|
void this.config.get('discovery').onMetadata?.(meta, this.platform);
|
|
@@ -70,82 +74,120 @@ export class MetadataDiscovery {
|
|
|
70
74
|
.sort((a, b) => b.root.name.localeCompare(a.root.name))
|
|
71
75
|
.forEach(meta => {
|
|
72
76
|
this.platform.validateMetadata(meta);
|
|
73
|
-
discovered.set(meta.
|
|
77
|
+
discovered.set(meta.class, meta);
|
|
74
78
|
});
|
|
79
|
+
for (const meta of discovered) {
|
|
80
|
+
meta.root = discovered.get(meta.root.class);
|
|
81
|
+
if (meta.inheritanceType === 'tpt') {
|
|
82
|
+
this.computeTPTOwnProps(meta);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
75
85
|
return discovered;
|
|
76
86
|
}
|
|
87
|
+
initAccessors(meta) {
|
|
88
|
+
for (const prop of Object.values(meta.properties)) {
|
|
89
|
+
if (!prop.accessor || meta.properties[prop.accessor]) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const desc = Object.getOwnPropertyDescriptor(meta.prototype, prop.name);
|
|
93
|
+
if (desc?.get || desc?.set) {
|
|
94
|
+
this.initFieldName(prop);
|
|
95
|
+
const accessor = prop.name;
|
|
96
|
+
prop.name = typeof prop.accessor === 'string' ? prop.accessor : prop.name;
|
|
97
|
+
if (prop.accessor === true) {
|
|
98
|
+
prop.getter = prop.setter = true;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
prop.getter = prop.setter = false;
|
|
102
|
+
}
|
|
103
|
+
prop.accessor = accessor;
|
|
104
|
+
prop.serializedName ??= accessor;
|
|
105
|
+
Utils.renameKey(meta.properties, accessor, prop.name);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const name = prop.name;
|
|
109
|
+
prop.name = prop.accessor;
|
|
110
|
+
this.initFieldName(prop);
|
|
111
|
+
prop.serializedName ??= prop.accessor;
|
|
112
|
+
prop.name = name;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
77
116
|
processDiscoveredEntities(discovered) {
|
|
78
117
|
for (const meta of discovered) {
|
|
79
118
|
let i = 1;
|
|
80
119
|
Object.values(meta.properties).forEach(prop => meta.propertyOrder.set(prop.name, i++));
|
|
81
120
|
Object.values(meta.properties).forEach(prop => this.initPolyEmbeddables(prop, discovered));
|
|
121
|
+
this.initAccessors(meta);
|
|
82
122
|
}
|
|
83
123
|
// ignore base entities (not annotated with @Entity)
|
|
84
124
|
const filtered = discovered.filter(meta => meta.root.name);
|
|
85
125
|
// sort so we discover entities first to get around issues with nested embeddables
|
|
86
126
|
filtered.sort((a, b) => !a.embeddable === !b.embeddable ? 0 : (a.embeddable ? 1 : -1));
|
|
87
127
|
filtered.forEach(meta => this.initSingleTableInheritance(meta, filtered));
|
|
128
|
+
filtered.forEach(meta => this.initTPTRelationships(meta, filtered));
|
|
88
129
|
filtered.forEach(meta => this.defineBaseEntityProperties(meta));
|
|
89
|
-
filtered.forEach(meta =>
|
|
130
|
+
filtered.forEach(meta => {
|
|
131
|
+
const newMeta = EntitySchema.fromMetadata(meta).init().meta;
|
|
132
|
+
return this.metadata.set(newMeta.class, newMeta);
|
|
133
|
+
});
|
|
90
134
|
filtered.forEach(meta => this.initAutoincrement(meta));
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
135
|
+
const forEachProp = (cb) => {
|
|
136
|
+
filtered.forEach(meta => Object.values(meta.properties).forEach(prop => cb(meta, prop)));
|
|
137
|
+
};
|
|
138
|
+
forEachProp((m, p) => this.initFactoryField(m, p));
|
|
139
|
+
forEachProp((m, p) => this.initPolymorphicRelation(m, p, filtered));
|
|
140
|
+
forEachProp((_m, p) => this.initRelation(p));
|
|
141
|
+
forEachProp((m, p) => this.initEmbeddables(m, p));
|
|
142
|
+
forEachProp((_m, p) => this.initFieldName(p));
|
|
143
|
+
filtered.forEach(meta => this.finalizeTPTInheritance(meta, filtered));
|
|
144
|
+
filtered.forEach(meta => this.computeTPTOwnProps(meta));
|
|
145
|
+
forEachProp((m, p) => this.initVersionProperty(m, p));
|
|
146
|
+
forEachProp((m, p) => this.initCustomType(m, p));
|
|
147
|
+
forEachProp((m, p) => this.initGeneratedColumn(m, p));
|
|
97
148
|
filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
|
|
98
149
|
filtered.forEach(meta => this.initCheckConstraints(meta));
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
prop.trackChanges = false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initIndexes(meta, prop)));
|
|
150
|
+
forEachProp((_m, p) => {
|
|
151
|
+
this.initDefaultValue(p);
|
|
152
|
+
this.inferTypeFromDefault(p);
|
|
153
|
+
this.initRelation(p);
|
|
154
|
+
this.initColumnType(p);
|
|
155
|
+
});
|
|
156
|
+
forEachProp((m, p) => this.initIndexes(m, p));
|
|
111
157
|
filtered.forEach(meta => this.autoWireBidirectionalProperties(meta));
|
|
112
|
-
filtered.forEach(meta => this.findReferencingProperties(meta, filtered));
|
|
113
158
|
for (const meta of filtered) {
|
|
114
159
|
discovered.push(...this.processEntity(meta));
|
|
115
160
|
}
|
|
116
161
|
discovered.forEach(meta => meta.sync(true));
|
|
117
|
-
|
|
118
|
-
// override the path in the options, so we can log it from the CLI in `cache:generate` command
|
|
119
|
-
if (combinedCachePath) {
|
|
120
|
-
this.config.get('metadataCache').combined = combinedCachePath;
|
|
121
|
-
}
|
|
162
|
+
this.metadataProvider.combineCache();
|
|
122
163
|
return discovered.map(meta => {
|
|
123
|
-
meta = this.metadata.get(meta.
|
|
164
|
+
meta = this.metadata.get(meta.class);
|
|
124
165
|
meta.sync(true);
|
|
166
|
+
this.findReferencingProperties(meta, filtered);
|
|
167
|
+
if (meta.inheritanceType === 'tpt') {
|
|
168
|
+
this.computeTPTOwnProps(meta);
|
|
169
|
+
}
|
|
125
170
|
return meta;
|
|
126
171
|
});
|
|
127
172
|
}
|
|
128
|
-
findEntities(preferTs
|
|
129
|
-
this.
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const paths =
|
|
133
|
-
const
|
|
173
|
+
async findEntities(preferTs) {
|
|
174
|
+
const { entities, entitiesTs, baseDir } = this.config.getAll();
|
|
175
|
+
const targets = (preferTs && entitiesTs.length > 0) ? entitiesTs : entities;
|
|
176
|
+
const processed = [];
|
|
177
|
+
const paths = [];
|
|
178
|
+
for (const entity of targets) {
|
|
179
|
+
if (typeof entity === 'string') {
|
|
180
|
+
paths.push(entity);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
processed.push(entity);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
134
186
|
if (paths.length > 0) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
return this.discoverDirectories(paths).then(() => {
|
|
139
|
-
this.discoverReferences(refs);
|
|
140
|
-
this.discoverMissingTargets();
|
|
141
|
-
this.validator.validateDiscovered(this.discovered, options);
|
|
142
|
-
return this.discovered;
|
|
143
|
-
});
|
|
187
|
+
const { discoverEntities } = await import('@mikro-orm/core/file-discovery');
|
|
188
|
+
processed.push(...await discoverEntities(paths, { baseDir }));
|
|
144
189
|
}
|
|
145
|
-
this.discoverReferences(
|
|
146
|
-
this.discoverMissingTargets();
|
|
147
|
-
this.validator.validateDiscovered(this.discovered, options);
|
|
148
|
-
return this.discovered;
|
|
190
|
+
return this.discoverReferences(processed);
|
|
149
191
|
}
|
|
150
192
|
discoverMissingTargets() {
|
|
151
193
|
const unwrap = (type) => type
|
|
@@ -154,17 +196,22 @@ export class MetadataDiscovery {
|
|
|
154
196
|
.replace(/\((.*)\)/, '$1'); // unwrap union types
|
|
155
197
|
const missing = [];
|
|
156
198
|
this.discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
|
|
157
|
-
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
199
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity) {
|
|
200
|
+
const pivotEntity = prop.pivotEntity;
|
|
201
|
+
const target = typeof pivotEntity === 'function' && !pivotEntity.prototype
|
|
202
|
+
? pivotEntity()
|
|
203
|
+
: pivotEntity;
|
|
204
|
+
if (!this.discovered.find(m => m.className === Utils.className(target))) {
|
|
205
|
+
missing.push(target);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (prop.kind !== ReferenceKind.SCALAR) {
|
|
209
|
+
const target = typeof prop.entity === 'function' && !prop.entity.prototype
|
|
165
210
|
? prop.entity()
|
|
166
211
|
: prop.type;
|
|
167
|
-
|
|
212
|
+
if (!unwrap(prop.type).split(/ ?\| ?/).every(type => this.discovered.find(m => m.className === type))) {
|
|
213
|
+
missing.push(...Utils.asArray(target));
|
|
214
|
+
}
|
|
168
215
|
}
|
|
169
216
|
}));
|
|
170
217
|
if (missing.length > 0) {
|
|
@@ -173,123 +220,107 @@ export class MetadataDiscovery {
|
|
|
173
220
|
}
|
|
174
221
|
tryDiscoverTargets(targets) {
|
|
175
222
|
for (const target of targets) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const files = await glob(paths, { cwd: Utils.normalizePath(this.config.get('baseDir')) });
|
|
185
|
-
this.logger.log('discovery', `- processing ${colors.cyan('' + files.length)} files`);
|
|
186
|
-
const found = [];
|
|
187
|
-
for (const filepath of files) {
|
|
188
|
-
const filename = basename(filepath);
|
|
189
|
-
if (!filename.match(/\.[cm]?[jt]s$/) ||
|
|
190
|
-
filename.endsWith('.js.map') ||
|
|
191
|
-
filename.match(/\.d\.[cm]?ts/) ||
|
|
192
|
-
filename.startsWith('.') ||
|
|
193
|
-
filename.match(/index\.[cm]?[jt]s$/)) {
|
|
194
|
-
this.logger.log('discovery', `- ignoring file ${filename}`);
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
const name = this.namingStrategy.getClassName(filename);
|
|
198
|
-
const path = Utils.normalizePath(this.config.get('baseDir'), filepath);
|
|
199
|
-
const targets = await this.getEntityClassOrSchema(path, name);
|
|
200
|
-
for (const target of targets) {
|
|
201
|
-
if (!(target instanceof Function) && !(target instanceof EntitySchema)) {
|
|
202
|
-
this.logger.log('discovery', `- ignoring file ${filename}`);
|
|
203
|
-
continue;
|
|
223
|
+
const schema = target instanceof EntitySchema ? target : undefined;
|
|
224
|
+
const isDiscoverable = typeof target === 'function' || schema;
|
|
225
|
+
if (isDiscoverable && target.name) {
|
|
226
|
+
// Get the actual class for EntitySchema, or use target directly for classes
|
|
227
|
+
const targetClass = schema ? schema.meta.class : target;
|
|
228
|
+
if (!this.metadata.has(targetClass)) {
|
|
229
|
+
this.discoverReferences([target], false);
|
|
230
|
+
this.discoverMissingTargets();
|
|
204
231
|
}
|
|
205
|
-
const entity = this.prepare(target);
|
|
206
|
-
const schema = this.getSchema(entity, path);
|
|
207
|
-
const meta = schema.init().meta;
|
|
208
|
-
this.metadata.set(meta.className, meta);
|
|
209
|
-
found.push([schema, path]);
|
|
210
232
|
}
|
|
211
233
|
}
|
|
212
|
-
for (const [schema, path] of found) {
|
|
213
|
-
this.discoverEntity(schema, path);
|
|
214
|
-
}
|
|
215
234
|
}
|
|
216
|
-
discoverReferences(refs) {
|
|
235
|
+
discoverReferences(refs, validate = true) {
|
|
217
236
|
const found = [];
|
|
218
237
|
for (const entity of refs) {
|
|
219
|
-
|
|
238
|
+
if (typeof entity === 'string') {
|
|
239
|
+
throw new Error('Folder based discovery requires the async `MikroORM.init()` method.');
|
|
240
|
+
}
|
|
241
|
+
const schema = this.getSchema(entity);
|
|
220
242
|
const meta = schema.init().meta;
|
|
221
|
-
this.metadata.set(meta.
|
|
243
|
+
this.metadata.set(meta.class, meta);
|
|
222
244
|
found.push(schema);
|
|
223
245
|
}
|
|
224
246
|
// discover parents (base entities) automatically
|
|
225
247
|
for (const meta of this.metadata) {
|
|
226
248
|
let parent = meta.extends;
|
|
227
|
-
if (parent instanceof EntitySchema && !this.metadata.has(parent.meta.
|
|
228
|
-
this.discoverReferences([parent]);
|
|
249
|
+
if (parent instanceof EntitySchema && !this.metadata.has(parent.init().meta.class)) {
|
|
250
|
+
this.discoverReferences([parent], false);
|
|
229
251
|
}
|
|
230
|
-
|
|
252
|
+
if (typeof parent === 'function' && parent.name && !this.metadata.has(parent)) {
|
|
253
|
+
this.discoverReferences([parent], false);
|
|
254
|
+
}
|
|
255
|
+
/* v8 ignore next */
|
|
231
256
|
if (!meta.class) {
|
|
232
257
|
continue;
|
|
233
258
|
}
|
|
234
259
|
parent = Object.getPrototypeOf(meta.class);
|
|
235
|
-
if
|
|
236
|
-
|
|
260
|
+
// Skip if parent is the auto-generated base class for the same entity (from setClass usage)
|
|
261
|
+
if (parent.name !== '' && parent.name !== meta.className && !this.metadata.has(parent) && parent !== BaseEntity) {
|
|
262
|
+
this.discoverReferences([parent], false);
|
|
237
263
|
}
|
|
238
264
|
}
|
|
239
265
|
for (const schema of found) {
|
|
240
266
|
this.discoverEntity(schema);
|
|
241
267
|
}
|
|
268
|
+
this.discoverMissingTargets();
|
|
269
|
+
if (validate) {
|
|
270
|
+
this.validator.validateDiscovered(this.discovered, this.config.get('discovery'));
|
|
271
|
+
}
|
|
242
272
|
return this.discovered.filter(meta => found.find(m => m.name === meta.className));
|
|
243
273
|
}
|
|
244
|
-
reset(
|
|
245
|
-
const exists = this.discovered.findIndex(m => m.className === className);
|
|
274
|
+
reset(entityName) {
|
|
275
|
+
const exists = this.discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
|
|
246
276
|
if (exists !== -1) {
|
|
247
|
-
this.metadata.reset(this.discovered[exists].
|
|
277
|
+
this.metadata.reset(this.discovered[exists].class);
|
|
248
278
|
this.discovered.splice(exists, 1);
|
|
249
279
|
}
|
|
250
280
|
}
|
|
251
|
-
|
|
252
|
-
/* v8 ignore next 3 */
|
|
253
|
-
if ('schema' in entity && entity.schema instanceof EntitySchema) {
|
|
254
|
-
return entity.schema;
|
|
255
|
-
}
|
|
281
|
+
getSchema(entity) {
|
|
256
282
|
if (EntitySchema.REGISTRY.has(entity)) {
|
|
257
|
-
|
|
283
|
+
entity = EntitySchema.REGISTRY.get(entity);
|
|
258
284
|
}
|
|
259
|
-
return entity;
|
|
260
|
-
}
|
|
261
|
-
getSchema(entity, filepath) {
|
|
262
285
|
if (entity instanceof EntitySchema) {
|
|
263
|
-
if (filepath) {
|
|
264
|
-
// initialize global metadata for given entity
|
|
265
|
-
MetadataStorage.getMetadata(entity.meta.className, filepath);
|
|
266
|
-
}
|
|
267
286
|
const meta = Utils.copy(entity.meta, false);
|
|
268
287
|
return EntitySchema.fromMetadata(meta);
|
|
269
288
|
}
|
|
270
289
|
const path = entity[MetadataStorage.PATH_SYMBOL];
|
|
271
290
|
if (path) {
|
|
272
291
|
const meta = Utils.copy(MetadataStorage.getMetadata(entity.name, path), false);
|
|
273
|
-
meta.path =
|
|
274
|
-
this.metadata.set(entity
|
|
292
|
+
meta.path = path;
|
|
293
|
+
this.metadata.set(entity, meta);
|
|
275
294
|
}
|
|
276
|
-
const exists = this.metadata.has(entity
|
|
277
|
-
const meta = this.metadata.get(entity
|
|
295
|
+
const exists = this.metadata.has(entity);
|
|
296
|
+
const meta = this.metadata.get(entity, true);
|
|
278
297
|
meta.abstract ??= !(exists && meta.name);
|
|
279
298
|
const schema = EntitySchema.fromMetadata(meta);
|
|
280
299
|
schema.setClass(entity);
|
|
281
300
|
return schema;
|
|
282
301
|
}
|
|
283
|
-
|
|
284
|
-
|
|
302
|
+
getRootEntity(meta) {
|
|
303
|
+
const base = meta.extends && this.metadata.find(meta.extends);
|
|
304
|
+
if (!base || base === meta) { // make sure we do not fall into infinite loop
|
|
305
|
+
return meta;
|
|
306
|
+
}
|
|
307
|
+
const root = this.getRootEntity(base);
|
|
308
|
+
// For STI or TPT, use the root entity.
|
|
309
|
+
// Check both `inheritanceType` (set during discovery) and raw `inheritance` option (set before discovery).
|
|
310
|
+
if (root.discriminatorColumn || root.inheritanceType || root.inheritance === 'tpt') {
|
|
311
|
+
return root;
|
|
312
|
+
}
|
|
313
|
+
return meta;
|
|
314
|
+
}
|
|
315
|
+
discoverEntity(schema) {
|
|
285
316
|
const meta = schema.meta;
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
const
|
|
317
|
+
const path = meta.path;
|
|
318
|
+
this.logger.log('discovery', `- processing entity ${colors.cyan(meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
|
|
319
|
+
const root = this.getRootEntity(meta);
|
|
320
|
+
schema.meta.path = meta.path;
|
|
321
|
+
const cache = this.metadataProvider.getCachedMetadata(meta, root);
|
|
289
322
|
if (cache) {
|
|
290
323
|
this.logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
|
|
291
|
-
this.metadataProvider.loadFromCache(meta, cache);
|
|
292
|
-
meta.root = root;
|
|
293
324
|
this.discovered.push(meta);
|
|
294
325
|
return;
|
|
295
326
|
}
|
|
@@ -298,50 +329,18 @@ export class MetadataDiscovery {
|
|
|
298
329
|
this.inferDefaultValue(meta, prop);
|
|
299
330
|
}
|
|
300
331
|
// if the definition is using EntitySchema we still want it to go through the metadata provider to validate no types are missing
|
|
301
|
-
this.metadataProvider.loadEntityMetadata(meta
|
|
302
|
-
if (!meta.
|
|
332
|
+
this.metadataProvider.loadEntityMetadata(meta);
|
|
333
|
+
if (!meta.tableName && meta.name) {
|
|
303
334
|
const entityName = root.discriminatorColumn ? root.name : meta.name;
|
|
304
|
-
meta.
|
|
335
|
+
meta.tableName = this.namingStrategy.classToTableName(entityName);
|
|
305
336
|
}
|
|
306
|
-
|
|
307
|
-
this.saveToCache(meta);
|
|
337
|
+
this.metadataProvider.saveToCache(meta);
|
|
308
338
|
meta.root = root;
|
|
309
339
|
this.discovered.push(meta);
|
|
310
340
|
}
|
|
311
|
-
saveToCache(meta) {
|
|
312
|
-
if (!this.metadataProvider.useCache()) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
const copy = Utils.copy(meta, false);
|
|
316
|
-
for (const prop of copy.props) {
|
|
317
|
-
if (Type.isMappedType(prop.type)) {
|
|
318
|
-
Reflect.deleteProperty(prop, 'type');
|
|
319
|
-
Reflect.deleteProperty(prop, 'customType');
|
|
320
|
-
}
|
|
321
|
-
if (prop.default) {
|
|
322
|
-
const raw = RawQueryFragment.getKnownFragment(prop.default);
|
|
323
|
-
if (raw) {
|
|
324
|
-
prop.defaultRaw ??= this.platform.formatQuery(raw.sql, raw.params);
|
|
325
|
-
Reflect.deleteProperty(prop, 'default');
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
Reflect.deleteProperty(prop, 'targetMeta');
|
|
329
|
-
}
|
|
330
|
-
[
|
|
331
|
-
'prototype', 'props', 'referencingProperties', 'propertyOrder', 'relations',
|
|
332
|
-
'concurrencyCheckKeys', 'checks',
|
|
333
|
-
].forEach(key => delete copy[key]);
|
|
334
|
-
// base entity without properties might not have path, but nothing to cache there
|
|
335
|
-
if (meta.path) {
|
|
336
|
-
this.cache.set(meta.className + extname(meta.path), copy, meta.path);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
341
|
initNullability(prop) {
|
|
340
|
-
if (prop.kind === ReferenceKind.MANY_TO_ONE) {
|
|
341
|
-
return Utils.defaultValue(prop, 'nullable', prop.optional || prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL));
|
|
342
|
-
}
|
|
343
342
|
if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
|
344
|
-
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner
|
|
343
|
+
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner);
|
|
345
344
|
}
|
|
346
345
|
return Utils.defaultValue(prop, 'nullable', prop.optional);
|
|
347
346
|
}
|
|
@@ -365,6 +364,12 @@ export class MetadataDiscovery {
|
|
|
365
364
|
if (!prop.joinColumns || !prop.columnTypes || prop.ownColumns || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
366
365
|
continue;
|
|
367
366
|
}
|
|
367
|
+
// For polymorphic relations, ownColumns should include all fieldNames
|
|
368
|
+
// (discriminator + ID columns) since they are all owned by this relation
|
|
369
|
+
if (prop.polymorphic) {
|
|
370
|
+
prop.ownColumns = prop.fieldNames;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
368
373
|
if (prop.joinColumns.length > 1) {
|
|
369
374
|
prop.ownColumns = prop.joinColumns.filter(col => {
|
|
370
375
|
return !meta.props.find(p => p.name !== prop.name && (!p.fieldNames || p.fieldNames.includes(col)));
|
|
@@ -376,7 +381,7 @@ export class MetadataDiscovery {
|
|
|
376
381
|
if (prop.joinColumns.length !== prop.columnTypes.length) {
|
|
377
382
|
prop.columnTypes = prop.joinColumns.flatMap(field => {
|
|
378
383
|
const matched = meta.props.find(p => p.fieldNames?.includes(field));
|
|
379
|
-
/* v8 ignore next
|
|
384
|
+
/* v8 ignore next */
|
|
380
385
|
if (!matched) {
|
|
381
386
|
throw MetadataError.fromWrongForeignKey(meta, prop, 'columnTypes');
|
|
382
387
|
}
|
|
@@ -395,30 +400,30 @@ export class MetadataDiscovery {
|
|
|
395
400
|
if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
|
|
396
401
|
prop.fieldNames = [this.namingStrategy.propertyToColumnName(prop.name, object)];
|
|
397
402
|
}
|
|
398
|
-
else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
403
|
+
else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
|
|
399
404
|
prop.fieldNames = this.initManyToOneFieldName(prop, prop.name);
|
|
400
405
|
}
|
|
401
406
|
else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
|
|
402
407
|
prop.fieldNames = this.initManyToManyFieldName(prop, prop.name);
|
|
403
408
|
}
|
|
404
409
|
}
|
|
405
|
-
initManyToOneFieldName(prop, name) {
|
|
406
|
-
const meta2 =
|
|
410
|
+
initManyToOneFieldName(prop, name, tableName) {
|
|
411
|
+
const meta2 = prop.targetMeta;
|
|
407
412
|
const ret = [];
|
|
408
413
|
for (const primaryKey of meta2.primaryKeys) {
|
|
409
414
|
this.initFieldName(meta2.properties[primaryKey]);
|
|
410
415
|
for (const fieldName of meta2.properties[primaryKey].fieldNames) {
|
|
411
|
-
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
|
|
416
|
+
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK, tableName));
|
|
412
417
|
}
|
|
413
418
|
}
|
|
414
419
|
return ret;
|
|
415
420
|
}
|
|
416
421
|
initManyToManyFieldName(prop, name) {
|
|
417
|
-
const meta2 =
|
|
422
|
+
const meta2 = prop.targetMeta;
|
|
418
423
|
return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
|
|
419
424
|
}
|
|
420
425
|
initManyToManyFields(meta, prop) {
|
|
421
|
-
const meta2 =
|
|
426
|
+
const meta2 = prop.targetMeta;
|
|
422
427
|
Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
|
|
423
428
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
424
429
|
const props = Object.values(pivotMeta?.properties ?? {});
|
|
@@ -439,35 +444,77 @@ export class MetadataDiscovery {
|
|
|
439
444
|
prop.inverseJoinColumns ??= second.fieldNames;
|
|
440
445
|
}
|
|
441
446
|
if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
|
|
442
|
-
prop.pivotTable = this.namingStrategy.joinTableName(meta.
|
|
447
|
+
prop.pivotTable = this.namingStrategy.joinTableName(meta.className, meta2.tableName, prop.name, meta.tableName);
|
|
443
448
|
}
|
|
444
449
|
if (prop.mappedBy) {
|
|
445
450
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
446
451
|
this.initManyToManyFields(meta2, prop2);
|
|
447
452
|
prop.pivotTable = prop2.pivotTable;
|
|
448
|
-
prop.pivotEntity = prop2.pivotEntity
|
|
453
|
+
prop.pivotEntity = prop2.pivotEntity;
|
|
449
454
|
prop.fixedOrder = prop2.fixedOrder;
|
|
450
455
|
prop.fixedOrderColumn = prop2.fixedOrderColumn;
|
|
451
456
|
prop.joinColumns = prop2.inverseJoinColumns;
|
|
452
457
|
prop.inverseJoinColumns = prop2.joinColumns;
|
|
458
|
+
prop.polymorphic = prop2.polymorphic;
|
|
459
|
+
prop.discriminator = prop2.discriminator;
|
|
460
|
+
prop.discriminatorColumn = prop2.discriminatorColumn;
|
|
461
|
+
prop.discriminatorValue = prop2.discriminatorValue;
|
|
453
462
|
}
|
|
454
463
|
prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
|
|
455
|
-
|
|
456
|
-
prop.
|
|
464
|
+
// For polymorphic M:N, use discriminator base name for FK column (e.g., taggable_id instead of post_id)
|
|
465
|
+
if (prop.polymorphic && prop.discriminator) {
|
|
466
|
+
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(prop.discriminator, referencedColumnName, prop.referencedColumnNames.length > 1));
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
const ownerTableName = this.isExplicitTableName(meta.root) ? meta.root.tableName : undefined;
|
|
470
|
+
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
|
|
471
|
+
}
|
|
472
|
+
const inverseTableName = this.isExplicitTableName(meta2.root) ? meta2.root.tableName : undefined;
|
|
473
|
+
prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className, inverseTableName);
|
|
474
|
+
}
|
|
475
|
+
isExplicitTableName(meta) {
|
|
476
|
+
return meta.tableName !== this.namingStrategy.classToTableName(meta.className);
|
|
457
477
|
}
|
|
458
478
|
initManyToOneFields(prop) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
479
|
+
if (prop.polymorphic && prop.polymorphTargets) {
|
|
480
|
+
const fieldNames1 = prop.targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
|
|
481
|
+
const idColumns = fieldNames1.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, fieldNames1.length > 1));
|
|
482
|
+
prop.fieldNames ??= [prop.discriminatorColumn, ...idColumns];
|
|
483
|
+
prop.joinColumns ??= idColumns;
|
|
484
|
+
prop.referencedColumnNames ??= fieldNames1;
|
|
485
|
+
prop.referencedTableName ??= prop.targetMeta.tableName;
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const meta2 = prop.targetMeta;
|
|
489
|
+
let fieldNames;
|
|
490
|
+
// If targetKey is specified, use that property's field names instead of PKs
|
|
491
|
+
if (prop.targetKey) {
|
|
492
|
+
const targetProp = meta2.properties[prop.targetKey];
|
|
493
|
+
fieldNames = targetProp.fieldNames;
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
|
|
497
|
+
}
|
|
498
|
+
Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
|
|
462
499
|
if (!prop.joinColumns) {
|
|
463
500
|
prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
|
|
464
501
|
}
|
|
465
502
|
if (!prop.referencedColumnNames) {
|
|
466
503
|
prop.referencedColumnNames = fieldNames;
|
|
467
504
|
}
|
|
505
|
+
// Relations to composite PK targets need cascade update by default,
|
|
506
|
+
// since composite PKs are more likely to have mutable components
|
|
507
|
+
if (meta2.compositePK) {
|
|
508
|
+
prop.updateRule ??= 'cascade';
|
|
509
|
+
}
|
|
510
|
+
// Nullable relations default to 'set null' on delete - when the referenced
|
|
511
|
+
// entity is deleted, set the FK to null rather than failing
|
|
512
|
+
if (prop.nullable) {
|
|
513
|
+
prop.deleteRule ??= 'set null';
|
|
514
|
+
}
|
|
468
515
|
}
|
|
469
516
|
initOneToManyFields(prop) {
|
|
470
|
-
const meta2 =
|
|
517
|
+
const meta2 = prop.targetMeta;
|
|
471
518
|
if (!prop.joinColumns) {
|
|
472
519
|
prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
|
|
473
520
|
}
|
|
@@ -480,12 +527,17 @@ export class MetadataDiscovery {
|
|
|
480
527
|
const pks = Object.values(meta.properties).filter(prop => prop.primary);
|
|
481
528
|
meta.primaryKeys = pks.map(prop => prop.name);
|
|
482
529
|
meta.compositePK = pks.length > 1;
|
|
483
|
-
// FK used as PK, we need to cascade
|
|
484
|
-
|
|
485
|
-
|
|
530
|
+
// FK used as PK, we need to cascade - applies to both single FK-as-PK
|
|
531
|
+
// and composite PKs where all PKs are FKs (e.g., pivot-like entities)
|
|
532
|
+
const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
|
|
533
|
+
if (fkPks.length > 0 && fkPks.length === pks.length) {
|
|
534
|
+
for (const pk of fkPks) {
|
|
535
|
+
pk.deleteRule ??= 'cascade';
|
|
536
|
+
pk.updateRule ??= 'cascade';
|
|
537
|
+
}
|
|
486
538
|
}
|
|
487
539
|
meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
|
|
488
|
-
this.validator.validateEntityDefinition(this.metadata, meta.
|
|
540
|
+
this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
|
|
489
541
|
for (const prop of Object.values(meta.properties)) {
|
|
490
542
|
this.initNullability(prop);
|
|
491
543
|
this.applyNamingStrategy(meta, prop);
|
|
@@ -498,15 +550,21 @@ export class MetadataDiscovery {
|
|
|
498
550
|
}
|
|
499
551
|
this.initOwnColumns(meta);
|
|
500
552
|
meta.simplePK = pks.length === 1 && pks[0].kind === ReferenceKind.SCALAR && !pks[0].customType && pks[0].runtimeType !== 'Date';
|
|
501
|
-
meta.serializedPrimaryKey
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
serializedPKProp.persist = false;
|
|
553
|
+
meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
|
|
554
|
+
if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
|
|
555
|
+
meta.properties[meta.serializedPrimaryKey].persist ??= false;
|
|
505
556
|
}
|
|
506
557
|
if (this.platform.usesPivotTable()) {
|
|
507
558
|
return Object.values(meta.properties)
|
|
508
559
|
.filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
|
|
509
|
-
.map(prop =>
|
|
560
|
+
.map(prop => {
|
|
561
|
+
const pivotMeta = this.definePivotTableEntity(meta, prop);
|
|
562
|
+
prop.pivotEntity = pivotMeta.class;
|
|
563
|
+
if (prop.inversedBy) {
|
|
564
|
+
prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
565
|
+
}
|
|
566
|
+
return pivotMeta;
|
|
567
|
+
});
|
|
510
568
|
}
|
|
511
569
|
return [];
|
|
512
570
|
}
|
|
@@ -523,8 +581,11 @@ export class MetadataDiscovery {
|
|
|
523
581
|
['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
|
|
524
582
|
const value = prop[type];
|
|
525
583
|
if (value instanceof Function) {
|
|
526
|
-
const meta2 = this.metadata.get(prop.
|
|
584
|
+
const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
|
|
527
585
|
prop[type] = value(meta2.properties)?.name;
|
|
586
|
+
if (type === 'pivotEntity' && value) {
|
|
587
|
+
prop[type] = value(meta2.properties);
|
|
588
|
+
}
|
|
528
589
|
if (prop[type] == null) {
|
|
529
590
|
throw MetadataError.fromWrongReference(meta, prop, type);
|
|
530
591
|
}
|
|
@@ -540,9 +601,9 @@ export class MetadataDiscovery {
|
|
|
540
601
|
}
|
|
541
602
|
else if (fks.length >= 2) {
|
|
542
603
|
[first, second] = fks;
|
|
543
|
-
/* v8 ignore next 3 */
|
|
544
604
|
}
|
|
545
605
|
else {
|
|
606
|
+
/* v8 ignore next */
|
|
546
607
|
return [];
|
|
547
608
|
}
|
|
548
609
|
// wrong FK order, first FK needs to point to the owning side
|
|
@@ -556,7 +617,9 @@ export class MetadataDiscovery {
|
|
|
556
617
|
return [first, second];
|
|
557
618
|
}
|
|
558
619
|
definePivotTableEntity(meta, prop) {
|
|
559
|
-
const pivotMeta =
|
|
620
|
+
const pivotMeta = prop.pivotEntity
|
|
621
|
+
? this.metadata.find(prop.pivotEntity)
|
|
622
|
+
: this.metadata.getByClassName(prop.pivotTable, false);
|
|
560
623
|
// ensure inverse side exists so we can join it when populating via pivot tables
|
|
561
624
|
if (!prop.inversedBy && prop.targetMeta) {
|
|
562
625
|
const inverseName = `${meta.className}_${prop.name}__inverse`;
|
|
@@ -565,6 +628,8 @@ export class MetadataDiscovery {
|
|
|
565
628
|
name: inverseName,
|
|
566
629
|
kind: ReferenceKind.MANY_TO_MANY,
|
|
567
630
|
type: meta.className,
|
|
631
|
+
target: meta.class,
|
|
632
|
+
targetMeta: meta,
|
|
568
633
|
mappedBy: prop.name,
|
|
569
634
|
pivotEntity: prop.pivotEntity,
|
|
570
635
|
pivotTable: prop.pivotTable,
|
|
@@ -573,55 +638,190 @@ export class MetadataDiscovery {
|
|
|
573
638
|
};
|
|
574
639
|
this.applyNamingStrategy(prop.targetMeta, inverseProp);
|
|
575
640
|
this.initCustomType(prop.targetMeta, inverseProp);
|
|
576
|
-
this.initRelation(inverseProp);
|
|
577
641
|
prop.targetMeta.properties[inverseName] = inverseProp;
|
|
578
642
|
}
|
|
579
643
|
if (pivotMeta) {
|
|
644
|
+
prop.pivotEntity = pivotMeta.class;
|
|
580
645
|
this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
|
|
646
|
+
if (prop.polymorphic && prop.discriminatorValue) {
|
|
647
|
+
pivotMeta.polymorphicDiscriminatorMap ??= {};
|
|
648
|
+
pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
|
|
649
|
+
// For composite PK entities sharing a polymorphic pivot table,
|
|
650
|
+
// we need to add columns for each entity type's PKs
|
|
651
|
+
this.addPolymorphicPivotColumns(pivotMeta, meta, prop);
|
|
652
|
+
// Add virtual M:1 relation for this polymorphic owner (for join loading)
|
|
653
|
+
const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
|
|
654
|
+
if (!pivotMeta.properties[ownerRelationName]) {
|
|
655
|
+
pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
581
658
|
return pivotMeta;
|
|
582
659
|
}
|
|
583
|
-
const exists = this.metadata.find(prop.pivotTable);
|
|
584
|
-
if (exists) {
|
|
585
|
-
prop.pivotEntity = exists.className;
|
|
586
|
-
return exists;
|
|
587
|
-
}
|
|
588
660
|
let tableName = prop.pivotTable;
|
|
589
661
|
let schemaName;
|
|
590
662
|
if (prop.pivotTable.includes('.')) {
|
|
591
663
|
[schemaName, tableName] = prop.pivotTable.split('.');
|
|
592
664
|
}
|
|
593
665
|
schemaName ??= meta.schema;
|
|
594
|
-
const
|
|
595
|
-
const
|
|
666
|
+
const targetMeta = prop.targetMeta;
|
|
667
|
+
const targetType = targetMeta.className;
|
|
668
|
+
const pivotMeta2 = new EntityMetadata({
|
|
596
669
|
name: prop.pivotTable,
|
|
597
670
|
className: prop.pivotTable,
|
|
598
671
|
collection: tableName,
|
|
599
672
|
schema: schemaName,
|
|
600
673
|
pivotTable: true,
|
|
601
674
|
});
|
|
602
|
-
prop.pivotEntity =
|
|
675
|
+
prop.pivotEntity = pivotMeta2.class;
|
|
603
676
|
if (prop.fixedOrder) {
|
|
604
|
-
const primaryProp = this.defineFixedOrderProperty(prop,
|
|
605
|
-
|
|
677
|
+
const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
|
|
678
|
+
pivotMeta2.properties[primaryProp.name] = primaryProp;
|
|
606
679
|
}
|
|
607
680
|
else {
|
|
608
|
-
|
|
681
|
+
pivotMeta2.compositePK = true;
|
|
609
682
|
}
|
|
610
683
|
// handle self-referenced m:n with same default field names
|
|
611
684
|
if (meta.className === targetType && prop.joinColumns.every((joinColumn, idx) => joinColumn === prop.inverseJoinColumns[idx])) {
|
|
612
|
-
|
|
613
|
-
|
|
685
|
+
// use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
|
|
686
|
+
const baseName = this.isExplicitTableName(meta) ? meta.tableName : meta.className;
|
|
687
|
+
prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
|
|
688
|
+
prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_2', name, meta.compositePK));
|
|
614
689
|
if (prop.inversedBy) {
|
|
615
|
-
const prop2 =
|
|
690
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
616
691
|
prop2.inverseJoinColumns = prop.joinColumns;
|
|
617
692
|
prop2.joinColumns = prop.inverseJoinColumns;
|
|
618
693
|
}
|
|
694
|
+
// propagate updated joinColumns to all child entities that inherit this property (STI)
|
|
695
|
+
for (const childMeta of this.discovered.filter(m => m.root === meta && m !== meta)) {
|
|
696
|
+
const childProp = childMeta.properties[prop.name];
|
|
697
|
+
if (childProp) {
|
|
698
|
+
childProp.joinColumns = prop.joinColumns;
|
|
699
|
+
childProp.inverseJoinColumns = prop.inverseJoinColumns;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
619
702
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
703
|
+
// For polymorphic M:N, create discriminator column and polymorphic FK
|
|
704
|
+
if (prop.polymorphic && prop.discriminatorColumn) {
|
|
705
|
+
this.definePolymorphicPivotProperties(pivotMeta2, meta, prop, targetMeta);
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
|
|
709
|
+
pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
|
|
710
|
+
}
|
|
711
|
+
return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
|
|
623
712
|
}
|
|
624
|
-
|
|
713
|
+
/**
|
|
714
|
+
* Create a scalar property for a pivot table column.
|
|
715
|
+
*/
|
|
716
|
+
createPivotScalarProperty(name, columnTypes, fieldNames = [name], options = {}) {
|
|
717
|
+
return {
|
|
718
|
+
name,
|
|
719
|
+
fieldNames,
|
|
720
|
+
columnTypes,
|
|
721
|
+
type: options.type ?? 'number',
|
|
722
|
+
kind: ReferenceKind.SCALAR,
|
|
723
|
+
primary: options.primary ?? false,
|
|
724
|
+
nullable: options.nullable ?? true,
|
|
725
|
+
...(options.persist !== undefined && { persist: options.persist }),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Get column types for an entity's primary keys, initializing them if needed.
|
|
730
|
+
*/
|
|
731
|
+
getPrimaryKeyColumnTypes(meta) {
|
|
732
|
+
const columnTypes = [];
|
|
733
|
+
for (const pk of meta.primaryKeys) {
|
|
734
|
+
const pkProp = meta.properties[pk];
|
|
735
|
+
this.initCustomType(meta, pkProp);
|
|
736
|
+
this.initColumnType(pkProp);
|
|
737
|
+
columnTypes.push(...pkProp.columnTypes);
|
|
738
|
+
}
|
|
739
|
+
return columnTypes;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Add missing FK columns for a polymorphic entity to an existing pivot table.
|
|
743
|
+
*/
|
|
744
|
+
addPolymorphicPivotColumns(pivotMeta, meta, prop) {
|
|
745
|
+
const existingFieldNames = new Set(Object.values(pivotMeta.properties).flatMap(p => p.fieldNames ?? []));
|
|
746
|
+
const columnTypes = this.getPrimaryKeyColumnTypes(meta);
|
|
747
|
+
for (let i = 0; i < prop.joinColumns.length; i++) {
|
|
748
|
+
const joinColumn = prop.joinColumns[i];
|
|
749
|
+
if (!existingFieldNames.has(joinColumn)) {
|
|
750
|
+
pivotMeta.properties[joinColumn] = this.createPivotScalarProperty(joinColumn, [columnTypes[i]]);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Define properties for a polymorphic pivot table.
|
|
756
|
+
*/
|
|
757
|
+
definePolymorphicPivotProperties(pivotMeta, meta, prop, targetMeta) {
|
|
758
|
+
const discriminatorColumn = prop.discriminatorColumn;
|
|
759
|
+
const isCompositePK = meta.compositePK;
|
|
760
|
+
// For composite PK polymorphic M:N, we need fixedOrder (auto-increment PK)
|
|
761
|
+
if (isCompositePK && !prop.fixedOrder) {
|
|
762
|
+
prop.fixedOrder = true;
|
|
763
|
+
const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
|
|
764
|
+
pivotMeta.properties[primaryProp.name] = primaryProp;
|
|
765
|
+
pivotMeta.compositePK = false;
|
|
766
|
+
}
|
|
767
|
+
const discriminatorProp = this.createPivotScalarProperty(discriminatorColumn, [this.platform.getVarcharTypeDeclarationSQL(prop)], [discriminatorColumn], { type: 'string', primary: !isCompositePK, nullable: false });
|
|
768
|
+
this.initFieldName(discriminatorProp);
|
|
769
|
+
pivotMeta.properties[discriminatorColumn] = discriminatorProp;
|
|
770
|
+
const columnTypes = this.getPrimaryKeyColumnTypes(meta);
|
|
771
|
+
if (isCompositePK) {
|
|
772
|
+
// Create separate properties for each PK column (nullable for other entity types)
|
|
773
|
+
for (let i = 0; i < prop.joinColumns.length; i++) {
|
|
774
|
+
pivotMeta.properties[prop.joinColumns[i]] = this.createPivotScalarProperty(prop.joinColumns[i], [columnTypes[i]]);
|
|
775
|
+
}
|
|
776
|
+
// Virtual property combining all columns (for compatibility)
|
|
777
|
+
pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, persist: false });
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, primary: true, nullable: false });
|
|
781
|
+
}
|
|
782
|
+
pivotMeta.properties[targetMeta.className + '_inverse'] = this.definePivotProperty(prop, targetMeta.className + '_inverse', targetMeta.class, prop.discriminator, false, false);
|
|
783
|
+
// Create virtual M:1 relation to the polymorphic owner for single-query join loading
|
|
784
|
+
const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
|
|
785
|
+
pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
|
|
786
|
+
pivotMeta.polymorphicDiscriminatorMap ??= {};
|
|
787
|
+
pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Create a virtual M:1 relation from pivot to a polymorphic owner entity.
|
|
791
|
+
* This enables single-query join loading for inverse-side polymorphic M:N.
|
|
792
|
+
*/
|
|
793
|
+
definePolymorphicOwnerRelation(prop, name, targetMeta) {
|
|
794
|
+
const ret = {
|
|
795
|
+
name,
|
|
796
|
+
type: targetMeta.className,
|
|
797
|
+
target: targetMeta.class,
|
|
798
|
+
kind: ReferenceKind.MANY_TO_ONE,
|
|
799
|
+
nullable: true,
|
|
800
|
+
owner: true,
|
|
801
|
+
primary: false,
|
|
802
|
+
createForeignKeyConstraint: false,
|
|
803
|
+
persist: false,
|
|
804
|
+
index: false,
|
|
805
|
+
};
|
|
806
|
+
ret.targetMeta = targetMeta;
|
|
807
|
+
ret.fieldNames = ret.joinColumns = ret.ownColumns = [...prop.joinColumns];
|
|
808
|
+
ret.referencedColumnNames = [];
|
|
809
|
+
ret.inverseJoinColumns = [];
|
|
810
|
+
for (const primaryKey of targetMeta.primaryKeys) {
|
|
811
|
+
const pkProp = targetMeta.properties[primaryKey];
|
|
812
|
+
ret.referencedColumnNames.push(...pkProp.fieldNames);
|
|
813
|
+
ret.inverseJoinColumns.push(...pkProp.fieldNames);
|
|
814
|
+
ret.length = pkProp.length;
|
|
815
|
+
ret.precision = pkProp.precision;
|
|
816
|
+
ret.scale = pkProp.scale;
|
|
817
|
+
}
|
|
818
|
+
const schema = targetMeta.schema ?? this.config.get('schema') ?? this.platform.getDefaultSchemaName();
|
|
819
|
+
ret.referencedTableName = schema && schema !== '*' ? schema + '.' + targetMeta.tableName : targetMeta.tableName;
|
|
820
|
+
this.initColumnType(ret);
|
|
821
|
+
this.initRelation(ret);
|
|
822
|
+
return ret;
|
|
823
|
+
}
|
|
824
|
+
defineFixedOrderProperty(prop, targetMeta) {
|
|
625
825
|
const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
|
|
626
826
|
const primaryProp = {
|
|
627
827
|
name: pk,
|
|
@@ -635,7 +835,7 @@ export class MetadataDiscovery {
|
|
|
635
835
|
this.initColumnType(primaryProp);
|
|
636
836
|
prop.fixedOrderColumn = pk;
|
|
637
837
|
if (prop.inversedBy) {
|
|
638
|
-
const prop2 =
|
|
838
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
639
839
|
prop2.fixedOrder = true;
|
|
640
840
|
prop2.fixedOrderColumn = pk;
|
|
641
841
|
}
|
|
@@ -644,7 +844,8 @@ export class MetadataDiscovery {
|
|
|
644
844
|
definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
|
|
645
845
|
const ret = {
|
|
646
846
|
name,
|
|
647
|
-
type,
|
|
847
|
+
type: Utils.className(type),
|
|
848
|
+
target: type,
|
|
648
849
|
kind: ReferenceKind.MANY_TO_ONE,
|
|
649
850
|
cascade: [Cascade.ALL],
|
|
650
851
|
fixedOrder: prop.fixedOrder,
|
|
@@ -656,10 +857,9 @@ export class MetadataDiscovery {
|
|
|
656
857
|
deleteRule: prop.deleteRule,
|
|
657
858
|
createForeignKeyConstraint: prop.createForeignKeyConstraint,
|
|
658
859
|
};
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
860
|
+
const defaultRule = selfReferencing && !this.platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
|
|
861
|
+
ret.updateRule ??= defaultRule;
|
|
862
|
+
ret.deleteRule ??= defaultRule;
|
|
663
863
|
const meta = this.metadata.get(type);
|
|
664
864
|
ret.targetMeta = meta;
|
|
665
865
|
ret.joinColumns = [];
|
|
@@ -702,7 +902,7 @@ export class MetadataDiscovery {
|
|
|
702
902
|
Object.values(meta.properties)
|
|
703
903
|
.filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
|
|
704
904
|
.forEach(prop => {
|
|
705
|
-
const meta2 =
|
|
905
|
+
const meta2 = prop.targetMeta;
|
|
706
906
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
707
907
|
if (prop2 && !prop2.inversedBy) {
|
|
708
908
|
prop2.inversedBy = prop.name;
|
|
@@ -710,7 +910,7 @@ export class MetadataDiscovery {
|
|
|
710
910
|
});
|
|
711
911
|
}
|
|
712
912
|
defineBaseEntityProperties(meta) {
|
|
713
|
-
const base = meta.extends && this.metadata.get(
|
|
913
|
+
const base = meta.extends && this.metadata.get(meta.extends);
|
|
714
914
|
if (!base || base === meta) { // make sure we do not fall into infinite loop
|
|
715
915
|
return 0;
|
|
716
916
|
}
|
|
@@ -741,12 +941,9 @@ export class MetadataDiscovery {
|
|
|
741
941
|
Utils.keys(base.hooks).forEach(type => {
|
|
742
942
|
meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
|
|
743
943
|
});
|
|
744
|
-
if (meta.constructorParams
|
|
944
|
+
if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
|
|
745
945
|
meta.constructorParams = [...base.constructorParams];
|
|
746
946
|
}
|
|
747
|
-
if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
|
|
748
|
-
meta.toJsonParams = [...base.toJsonParams];
|
|
749
|
-
}
|
|
750
947
|
return order;
|
|
751
948
|
}
|
|
752
949
|
initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
|
|
@@ -803,6 +1000,54 @@ export class MetadataDiscovery {
|
|
|
803
1000
|
polymorphs.forEach(meta => meta.root = embeddable);
|
|
804
1001
|
}
|
|
805
1002
|
}
|
|
1003
|
+
initPolymorphicRelation(meta, prop, discovered) {
|
|
1004
|
+
if (!prop.discriminator && !prop.discriminatorColumn && !prop.discriminatorMap && !Array.isArray(prop.target)) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
prop.polymorphic = true;
|
|
1008
|
+
prop.discriminator ??= prop.name;
|
|
1009
|
+
prop.discriminatorColumn ??= this.namingStrategy.discriminatorColumnName(prop.discriminator);
|
|
1010
|
+
prop.createForeignKeyConstraint = false;
|
|
1011
|
+
const isToOne = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind);
|
|
1012
|
+
if (isToOne) {
|
|
1013
|
+
const types = prop.type.split(/ ?\| ?/);
|
|
1014
|
+
prop.polymorphTargets = discovered.filter(m => types.includes(m.className) && !m.embeddable);
|
|
1015
|
+
prop.targetMeta = prop.polymorphTargets[0];
|
|
1016
|
+
prop.referencedPKs = prop.targetMeta?.primaryKeys;
|
|
1017
|
+
}
|
|
1018
|
+
if (prop.discriminatorMap) {
|
|
1019
|
+
const normalizedMap = {};
|
|
1020
|
+
for (const [key, value] of Object.entries(prop.discriminatorMap)) {
|
|
1021
|
+
const targetMeta = this.metadata.getByClassName(value, false);
|
|
1022
|
+
if (!targetMeta) {
|
|
1023
|
+
throw MetadataError.fromUnknownEntity(value, `${meta.className}.${prop.name} discriminatorMap`);
|
|
1024
|
+
}
|
|
1025
|
+
normalizedMap[key] = targetMeta.class;
|
|
1026
|
+
if (targetMeta.class === meta.class) {
|
|
1027
|
+
prop.discriminatorValue = key;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
prop.discriminatorMap = normalizedMap;
|
|
1031
|
+
}
|
|
1032
|
+
else if (isToOne) {
|
|
1033
|
+
prop.discriminatorMap = {};
|
|
1034
|
+
const tableNameToTarget = new Map();
|
|
1035
|
+
for (const target of prop.polymorphTargets) {
|
|
1036
|
+
const existing = tableNameToTarget.get(target.tableName);
|
|
1037
|
+
if (existing) {
|
|
1038
|
+
throw MetadataError.incompatiblePolymorphicTargets(meta, prop, existing, target, `both use table '${target.tableName}'. Use separate properties instead of a single polymorphic relation.`);
|
|
1039
|
+
}
|
|
1040
|
+
tableNameToTarget.set(target.tableName, target);
|
|
1041
|
+
prop.discriminatorMap[target.tableName] = target.class;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
prop.discriminatorValue ??= meta.tableName;
|
|
1046
|
+
if (!prop.discriminatorMap) {
|
|
1047
|
+
prop.discriminatorMap = { [prop.discriminatorValue]: meta.class };
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
806
1051
|
initEmbeddables(meta, embeddedProp, visited = new Set()) {
|
|
807
1052
|
if (embeddedProp.kind !== ReferenceKind.EMBEDDED || visited.has(embeddedProp)) {
|
|
808
1053
|
return;
|
|
@@ -838,7 +1083,7 @@ export class MetadataDiscovery {
|
|
|
838
1083
|
const glue = object ? '~' : '_';
|
|
839
1084
|
for (const prop of Object.values(embeddable.properties)) {
|
|
840
1085
|
const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
|
|
841
|
-
meta.properties[name] = Utils.copy(prop
|
|
1086
|
+
meta.properties[name] = Utils.copy(prop);
|
|
842
1087
|
meta.properties[name].name = name;
|
|
843
1088
|
meta.properties[name].embedded = [embeddedProp.name, prop.name];
|
|
844
1089
|
meta.propertyOrder.set(name, (order += 0.01));
|
|
@@ -875,10 +1120,12 @@ export class MetadataDiscovery {
|
|
|
875
1120
|
path = [embeddedProp.fieldNames[0]];
|
|
876
1121
|
}
|
|
877
1122
|
this.initFieldName(prop, true);
|
|
1123
|
+
this.initRelation(prop);
|
|
878
1124
|
path.push(prop.fieldNames[0]);
|
|
879
1125
|
meta.properties[name].fieldNames = prop.fieldNames;
|
|
880
1126
|
meta.properties[name].embeddedPath = path;
|
|
881
|
-
const
|
|
1127
|
+
const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
|
|
1128
|
+
const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
|
|
882
1129
|
meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
|
|
883
1130
|
meta.properties[name].persist = false; // only virtual as we store the whole object
|
|
884
1131
|
meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
|
|
@@ -906,7 +1153,7 @@ export class MetadataDiscovery {
|
|
|
906
1153
|
}
|
|
907
1154
|
initSingleTableInheritance(meta, metadata) {
|
|
908
1155
|
if (meta.root !== meta && !meta.__processed) {
|
|
909
|
-
meta.root = metadata.find(m => m.
|
|
1156
|
+
meta.root = metadata.find(m => m.class === meta.root.class);
|
|
910
1157
|
meta.root.__processed = true;
|
|
911
1158
|
}
|
|
912
1159
|
else {
|
|
@@ -915,17 +1162,24 @@ export class MetadataDiscovery {
|
|
|
915
1162
|
if (!meta.root.discriminatorColumn) {
|
|
916
1163
|
return;
|
|
917
1164
|
}
|
|
918
|
-
|
|
1165
|
+
meta.root.inheritanceType = 'sti';
|
|
1166
|
+
if (meta.root.discriminatorMap) {
|
|
1167
|
+
const map = meta.root.discriminatorMap;
|
|
1168
|
+
Object.keys(map)
|
|
1169
|
+
.filter(key => typeof map[key] === 'string')
|
|
1170
|
+
.forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
919
1173
|
meta.root.discriminatorMap = {};
|
|
920
1174
|
const children = metadata
|
|
921
|
-
.filter(m => m.root.
|
|
1175
|
+
.filter(m => m.root.class === meta.root.class && !m.abstract)
|
|
922
1176
|
.sort((a, b) => a.className.localeCompare(b.className));
|
|
923
1177
|
for (const m of children) {
|
|
924
1178
|
const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
|
|
925
|
-
meta.root.discriminatorMap[name] = m.
|
|
1179
|
+
meta.root.discriminatorMap[name] = m.class;
|
|
926
1180
|
}
|
|
927
1181
|
}
|
|
928
|
-
meta.discriminatorValue =
|
|
1182
|
+
meta.discriminatorValue = QueryHelper.findDiscriminatorValue(meta.root.discriminatorMap, meta.class);
|
|
929
1183
|
if (!meta.root.properties[meta.root.discriminatorColumn]) {
|
|
930
1184
|
this.createDiscriminatorProperty(meta.root);
|
|
931
1185
|
}
|
|
@@ -934,32 +1188,219 @@ export class MetadataDiscovery {
|
|
|
934
1188
|
if (meta.root === meta) {
|
|
935
1189
|
return;
|
|
936
1190
|
}
|
|
937
|
-
let i = 1;
|
|
938
1191
|
Object.values(meta.properties).forEach(prop => {
|
|
939
1192
|
const newProp = { ...prop };
|
|
940
|
-
|
|
1193
|
+
const rootProp = meta.root.properties[prop.name];
|
|
1194
|
+
if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
|
|
941
1195
|
const name = newProp.name;
|
|
942
1196
|
this.initFieldName(newProp, newProp.object);
|
|
943
|
-
newProp.
|
|
1197
|
+
newProp.renamedFrom = name;
|
|
1198
|
+
newProp.name = `${name}_${meta._id}`;
|
|
944
1199
|
meta.root.addProperty(newProp);
|
|
1200
|
+
this.initFieldName(prop, prop.object);
|
|
1201
|
+
// Track all field variants and map discriminator values to field names
|
|
1202
|
+
if (!rootProp.stiFieldNames) {
|
|
1203
|
+
this.initFieldName(rootProp, rootProp.object);
|
|
1204
|
+
rootProp.stiFieldNames = [...rootProp.fieldNames];
|
|
1205
|
+
rootProp.stiFieldNameMap = {};
|
|
1206
|
+
// Find which discriminator owns the original fieldNames
|
|
1207
|
+
for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
|
|
1208
|
+
const childMeta = this.metadata.find(childClass);
|
|
1209
|
+
if (childMeta?.properties[prop.name]?.fieldNames && compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
|
|
1210
|
+
rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
|
|
1216
|
+
rootProp.stiFieldNames.push(...prop.fieldNames);
|
|
945
1217
|
newProp.nullable = true;
|
|
946
1218
|
newProp.name = name;
|
|
947
1219
|
newProp.hydrate = false;
|
|
948
1220
|
newProp.inherited = true;
|
|
949
1221
|
return;
|
|
950
1222
|
}
|
|
951
|
-
if (prop.enum && prop.items &&
|
|
952
|
-
newProp.items = Utils.unique([...
|
|
1223
|
+
if (prop.enum && prop.items && rootProp?.items) {
|
|
1224
|
+
newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
|
|
953
1225
|
}
|
|
954
1226
|
newProp.nullable = true;
|
|
955
|
-
newProp.inherited =
|
|
1227
|
+
newProp.inherited = !rootProp;
|
|
956
1228
|
meta.root.addProperty(newProp);
|
|
957
1229
|
});
|
|
958
|
-
meta.
|
|
1230
|
+
meta.tableName = meta.root.tableName;
|
|
959
1231
|
meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
|
|
960
1232
|
meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
|
|
961
1233
|
meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
|
|
962
1234
|
}
|
|
1235
|
+
/**
|
|
1236
|
+
* First pass of TPT initialization: sets up hierarchy relationships
|
|
1237
|
+
* (inheritanceType, tptParent, tptChildren) before properties have fieldNames.
|
|
1238
|
+
*/
|
|
1239
|
+
initTPTRelationships(meta, metadata) {
|
|
1240
|
+
if (meta.root !== meta) {
|
|
1241
|
+
meta.root = metadata.find(m => m.class === meta.root.class);
|
|
1242
|
+
}
|
|
1243
|
+
const inheritance = meta.inheritance;
|
|
1244
|
+
if (inheritance === 'tpt' && meta.root === meta) {
|
|
1245
|
+
meta.inheritanceType = 'tpt';
|
|
1246
|
+
meta.tptChildren = [];
|
|
1247
|
+
}
|
|
1248
|
+
if (meta.root.inheritanceType !== 'tpt') {
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
const parent = this.getTPTParent(meta, metadata);
|
|
1252
|
+
if (parent) {
|
|
1253
|
+
meta.tptParent = parent;
|
|
1254
|
+
meta.inheritanceType = 'tpt';
|
|
1255
|
+
parent.tptChildren ??= [];
|
|
1256
|
+
if (!parent.tptChildren.includes(meta)) {
|
|
1257
|
+
parent.tptChildren.push(meta);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Second pass of TPT initialization: re-resolves metadata references after fieldNames
|
|
1263
|
+
* are set, syncs to registry metadata, and sets up discriminators.
|
|
1264
|
+
*/
|
|
1265
|
+
finalizeTPTInheritance(meta, metadata) {
|
|
1266
|
+
if (meta.inheritanceType !== 'tpt') {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
if (meta.tptParent) {
|
|
1270
|
+
meta.tptParent = metadata.find(m => m.class === meta.tptParent.class) ?? meta.tptParent;
|
|
1271
|
+
}
|
|
1272
|
+
if (meta.tptChildren) {
|
|
1273
|
+
meta.tptChildren = meta.tptChildren.map(child => metadata.find(m => m.class === child.class) ?? child);
|
|
1274
|
+
}
|
|
1275
|
+
const registryMeta = this.metadata.get(meta.class);
|
|
1276
|
+
if (registryMeta && registryMeta !== meta) {
|
|
1277
|
+
registryMeta.inheritanceType = meta.inheritanceType;
|
|
1278
|
+
registryMeta.tptParent = meta.tptParent ? this.metadata.get(meta.tptParent.class) : undefined;
|
|
1279
|
+
registryMeta.tptChildren = meta.tptChildren?.map(child => this.metadata.get(child.class));
|
|
1280
|
+
}
|
|
1281
|
+
this.initTPTDiscriminator(meta, metadata);
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Initialize TPT discriminator map and virtual discriminator property.
|
|
1285
|
+
* Unlike STI where the discriminator is a persisted column, TPT discriminator is computed
|
|
1286
|
+
* at query time using CASE WHEN expressions based on which child table has data.
|
|
1287
|
+
*/
|
|
1288
|
+
initTPTDiscriminator(meta, metadata) {
|
|
1289
|
+
const allDescendants = this.collectAllTPTDescendants(meta, metadata);
|
|
1290
|
+
allDescendants.sort((a, b) => this.getTPTDepth(b) - this.getTPTDepth(a));
|
|
1291
|
+
meta.allTPTDescendants = allDescendants;
|
|
1292
|
+
if (meta.root !== meta) {
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
meta.root.discriminatorMap = {};
|
|
1296
|
+
for (const m of allDescendants) {
|
|
1297
|
+
const name = this.namingStrategy.classToTableName(m.className);
|
|
1298
|
+
meta.root.discriminatorMap[name] = m.class;
|
|
1299
|
+
m.discriminatorValue = name;
|
|
1300
|
+
}
|
|
1301
|
+
if (!meta.abstract) {
|
|
1302
|
+
const name = this.namingStrategy.classToTableName(meta.className);
|
|
1303
|
+
meta.root.discriminatorMap[name] = meta.class;
|
|
1304
|
+
meta.discriminatorValue = name;
|
|
1305
|
+
}
|
|
1306
|
+
// Virtual discriminator property - computed at query time via CASE WHEN, not persisted
|
|
1307
|
+
const discriminatorColumn = '__tpt_type';
|
|
1308
|
+
if (!meta.root.properties[discriminatorColumn]) {
|
|
1309
|
+
meta.root.addProperty({
|
|
1310
|
+
name: discriminatorColumn,
|
|
1311
|
+
type: 'string',
|
|
1312
|
+
kind: ReferenceKind.SCALAR,
|
|
1313
|
+
persist: false,
|
|
1314
|
+
userDefined: false,
|
|
1315
|
+
hidden: true,
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
meta.root.tptDiscriminatorColumn = discriminatorColumn;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Recursively collect all TPT descendants (children, grandchildren, etc.)
|
|
1322
|
+
*/
|
|
1323
|
+
collectAllTPTDescendants(meta, metadata) {
|
|
1324
|
+
const descendants = [];
|
|
1325
|
+
const collect = (parent) => {
|
|
1326
|
+
for (const child of parent.tptChildren ?? []) {
|
|
1327
|
+
const resolved = metadata.find(m => m.class === child.class) ?? child;
|
|
1328
|
+
if (!resolved.abstract) {
|
|
1329
|
+
descendants.push(resolved);
|
|
1330
|
+
}
|
|
1331
|
+
collect(resolved);
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
collect(meta);
|
|
1335
|
+
return descendants;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Computes ownProps for TPT entities - only properties defined in THIS entity,
|
|
1339
|
+
* not inherited from parent. Also creates synthetic join properties for parent/child relationships.
|
|
1340
|
+
*
|
|
1341
|
+
* Called multiple times during discovery as metadata is progressively built.
|
|
1342
|
+
* Each pass overwrites earlier results to reflect the final state of properties.
|
|
1343
|
+
*/
|
|
1344
|
+
computeTPTOwnProps(meta) {
|
|
1345
|
+
if (meta.inheritanceType !== 'tpt') {
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
const belongsToTable = (prop) => prop.persist !== false || prop.primary;
|
|
1349
|
+
// Use meta.properties (object) since meta.props (array) may not be populated yet
|
|
1350
|
+
const allProps = Object.values(meta.properties);
|
|
1351
|
+
if (!meta.tptParent) {
|
|
1352
|
+
meta.ownProps = allProps.filter(belongsToTable);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
const parentPropNames = new Set(Object.values(meta.tptParent.properties).map(p => p.name));
|
|
1356
|
+
meta.ownProps = allProps.filter(prop => !parentPropNames.has(prop.name) && belongsToTable(prop));
|
|
1357
|
+
// Create synthetic join properties for the parent-child relationship
|
|
1358
|
+
const childFieldNames = meta.getPrimaryProps().flatMap(p => p.fieldNames);
|
|
1359
|
+
const parentFieldNames = meta.tptParent.getPrimaryProps().flatMap(p => p.fieldNames);
|
|
1360
|
+
meta.tptParentProp = {
|
|
1361
|
+
name: '[tpt:parent]',
|
|
1362
|
+
kind: ReferenceKind.MANY_TO_ONE,
|
|
1363
|
+
targetMeta: meta.tptParent,
|
|
1364
|
+
fieldNames: childFieldNames,
|
|
1365
|
+
referencedColumnNames: parentFieldNames,
|
|
1366
|
+
persist: false,
|
|
1367
|
+
};
|
|
1368
|
+
meta.tptInverseProp = {
|
|
1369
|
+
name: '[tpt:child]',
|
|
1370
|
+
kind: ReferenceKind.ONE_TO_ONE,
|
|
1371
|
+
owner: true,
|
|
1372
|
+
targetMeta: meta,
|
|
1373
|
+
fieldNames: parentFieldNames,
|
|
1374
|
+
joinColumns: parentFieldNames,
|
|
1375
|
+
referencedColumnNames: childFieldNames,
|
|
1376
|
+
persist: false,
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
/** Returns the depth of a TPT entity in its hierarchy (0 for root). */
|
|
1380
|
+
getTPTDepth(meta) {
|
|
1381
|
+
let depth = 0;
|
|
1382
|
+
let current = meta;
|
|
1383
|
+
while (current.tptParent) {
|
|
1384
|
+
depth++;
|
|
1385
|
+
current = current.tptParent;
|
|
1386
|
+
}
|
|
1387
|
+
return depth;
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Find the direct TPT parent entity for the given entity.
|
|
1391
|
+
*/
|
|
1392
|
+
getTPTParent(meta, metadata) {
|
|
1393
|
+
if (meta.root === meta) {
|
|
1394
|
+
return undefined;
|
|
1395
|
+
}
|
|
1396
|
+
return metadata.find(m => {
|
|
1397
|
+
const ext = meta.extends;
|
|
1398
|
+
if (ext instanceof EntitySchema) {
|
|
1399
|
+
return m.class === ext.meta.class || m.className === ext.meta.className;
|
|
1400
|
+
}
|
|
1401
|
+
return m.class === ext || m.className === Utils.className(ext);
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
963
1404
|
createDiscriminatorProperty(meta) {
|
|
964
1405
|
meta.addProperty({
|
|
965
1406
|
name: meta.discriminatorColumn,
|
|
@@ -976,18 +1417,28 @@ export class MetadataDiscovery {
|
|
|
976
1417
|
pks[0].autoincrement ??= true;
|
|
977
1418
|
}
|
|
978
1419
|
}
|
|
1420
|
+
createSchemaTable(meta) {
|
|
1421
|
+
const qualifiedName = meta.schema ? `${meta.schema}.${meta.tableName}` : meta.tableName;
|
|
1422
|
+
return {
|
|
1423
|
+
name: meta.tableName,
|
|
1424
|
+
schema: meta.schema,
|
|
1425
|
+
qualifiedName,
|
|
1426
|
+
toString: () => qualifiedName,
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
979
1429
|
initCheckConstraints(meta) {
|
|
980
|
-
const
|
|
1430
|
+
const columns = meta.createSchemaColumnMappingObject();
|
|
1431
|
+
const table = this.createSchemaTable(meta);
|
|
981
1432
|
for (const check of meta.checks) {
|
|
982
|
-
const
|
|
983
|
-
check.name ??= this.namingStrategy.indexName(meta.tableName,
|
|
1433
|
+
const fieldNames = check.property ? meta.properties[check.property].fieldNames : [];
|
|
1434
|
+
check.name ??= this.namingStrategy.indexName(meta.tableName, fieldNames, 'check');
|
|
984
1435
|
if (check.expression instanceof Function) {
|
|
985
|
-
check.expression = check.expression(
|
|
1436
|
+
check.expression = check.expression(columns, table);
|
|
986
1437
|
}
|
|
987
1438
|
}
|
|
988
1439
|
if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
|
|
989
1440
|
for (const prop of meta.props) {
|
|
990
|
-
if (prop.enum && !prop.nativeEnumName && prop.items?.every(item =>
|
|
1441
|
+
if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
|
|
991
1442
|
this.initFieldName(prop);
|
|
992
1443
|
meta.checks.push({
|
|
993
1444
|
name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
|
|
@@ -1012,16 +1463,16 @@ export class MetadataDiscovery {
|
|
|
1012
1463
|
}
|
|
1013
1464
|
return;
|
|
1014
1465
|
}
|
|
1015
|
-
const map = meta.createColumnMappingObject();
|
|
1016
1466
|
if (prop.generated instanceof Function) {
|
|
1017
|
-
|
|
1467
|
+
const columns = meta.createSchemaColumnMappingObject();
|
|
1468
|
+
prop.generated = prop.generated(columns, this.createSchemaTable(meta));
|
|
1018
1469
|
}
|
|
1019
1470
|
}
|
|
1020
1471
|
getDefaultVersionValue(meta, prop) {
|
|
1021
1472
|
if (typeof prop.defaultRaw !== 'undefined') {
|
|
1022
1473
|
return prop.defaultRaw;
|
|
1023
1474
|
}
|
|
1024
|
-
/* v8 ignore next
|
|
1475
|
+
/* v8 ignore next */
|
|
1025
1476
|
if (prop.default != null) {
|
|
1026
1477
|
return '' + this.platform.quoteVersionValue(prop.default, prop);
|
|
1027
1478
|
}
|
|
@@ -1034,10 +1485,6 @@ export class MetadataDiscovery {
|
|
|
1034
1485
|
return '1';
|
|
1035
1486
|
}
|
|
1036
1487
|
inferDefaultValue(meta, prop) {
|
|
1037
|
-
/* v8 ignore next 3 */
|
|
1038
|
-
if (!meta.class) {
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
1488
|
try {
|
|
1042
1489
|
// try to create two entity instances to detect the value is stable
|
|
1043
1490
|
const now = Date.now();
|
|
@@ -1065,7 +1512,7 @@ export class MetadataDiscovery {
|
|
|
1065
1512
|
return;
|
|
1066
1513
|
}
|
|
1067
1514
|
let val = prop.default;
|
|
1068
|
-
const raw =
|
|
1515
|
+
const raw = Raw.getKnownFragment(val);
|
|
1069
1516
|
if (raw) {
|
|
1070
1517
|
prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
|
|
1071
1518
|
return;
|
|
@@ -1112,51 +1559,61 @@ export class MetadataDiscovery {
|
|
|
1112
1559
|
}
|
|
1113
1560
|
// `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
|
|
1114
1561
|
if (typeof prop.type === 'function' && Type.isMappedType(prop.type.prototype) && !prop.customType) {
|
|
1115
|
-
|
|
1116
|
-
|
|
1562
|
+
// if the type is an ORM defined mapped type without `ensureComparable: true`,
|
|
1563
|
+
// we use just the type name, to have more performant hydration code
|
|
1564
|
+
const type = Utils.keys(t).find(type => {
|
|
1565
|
+
return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
|
|
1566
|
+
});
|
|
1567
|
+
if (type) {
|
|
1568
|
+
prop.type = type === 'datetime' ? 'Date' : type;
|
|
1569
|
+
}
|
|
1570
|
+
else {
|
|
1571
|
+
prop.customType = new prop.type();
|
|
1572
|
+
prop.type = prop.customType.constructor.name;
|
|
1573
|
+
}
|
|
1117
1574
|
}
|
|
1118
1575
|
if (simple) {
|
|
1119
1576
|
return;
|
|
1120
1577
|
}
|
|
1121
1578
|
if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
|
|
1122
|
-
prop.customType = new
|
|
1579
|
+
prop.customType = new t.json();
|
|
1123
1580
|
}
|
|
1124
1581
|
if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
|
|
1125
|
-
prop.customType = new
|
|
1582
|
+
prop.customType = new t.json();
|
|
1126
1583
|
}
|
|
1127
1584
|
if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
|
|
1128
|
-
prop.customType = new
|
|
1585
|
+
prop.customType = new t.json();
|
|
1129
1586
|
}
|
|
1130
1587
|
if (!prop.customType && prop.array && prop.items) {
|
|
1131
|
-
prop.customType = new
|
|
1588
|
+
prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
|
|
1132
1589
|
}
|
|
1133
1590
|
const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
|
|
1134
1591
|
if (objectEmbeddable && !prop.customType && isArray) {
|
|
1135
|
-
prop.customType = new
|
|
1592
|
+
prop.customType = new t.json();
|
|
1136
1593
|
}
|
|
1137
1594
|
// for number arrays we make sure to convert the items to numbers
|
|
1138
1595
|
if (!prop.customType && prop.type === 'number[]') {
|
|
1139
|
-
prop.customType = new
|
|
1596
|
+
prop.customType = new t.array(i => +i);
|
|
1140
1597
|
}
|
|
1141
1598
|
// `string[]` can be returned via ts-morph, while reflect metadata will give us just `array`
|
|
1142
1599
|
if (!prop.customType && isArray) {
|
|
1143
|
-
prop.customType = new
|
|
1600
|
+
prop.customType = new t.array();
|
|
1144
1601
|
}
|
|
1145
1602
|
if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
|
|
1146
|
-
prop.customType = new
|
|
1603
|
+
prop.customType = new t.blob();
|
|
1147
1604
|
}
|
|
1148
1605
|
if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
|
|
1149
|
-
prop.customType = new
|
|
1606
|
+
prop.customType = new t.uint8array();
|
|
1150
1607
|
}
|
|
1151
1608
|
const mappedType = this.getMappedType(prop);
|
|
1152
1609
|
if (prop.fieldNames?.length === 1 && !prop.customType) {
|
|
1153
|
-
[
|
|
1610
|
+
[t.bigint, t.double, t.decimal, t.interval, t.date]
|
|
1154
1611
|
.filter(type => mappedType instanceof type)
|
|
1155
|
-
.forEach(type => prop.customType = new type());
|
|
1612
|
+
.forEach((type) => prop.customType = new type());
|
|
1156
1613
|
}
|
|
1157
1614
|
if (prop.customType && !prop.columnTypes) {
|
|
1158
1615
|
const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
|
|
1159
|
-
if (prop.customType.compareAsType() === 'any' && ![
|
|
1616
|
+
if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
|
|
1160
1617
|
prop.runtimeType ??= mappedType.runtimeType;
|
|
1161
1618
|
}
|
|
1162
1619
|
else {
|
|
@@ -1176,16 +1633,16 @@ export class MetadataDiscovery {
|
|
|
1176
1633
|
prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
|
|
1177
1634
|
prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
1178
1635
|
prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
|
|
1179
|
-
if (prop.customType instanceof
|
|
1636
|
+
if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
|
|
1180
1637
|
prop.customType.mode = prop.runtimeType.toLowerCase();
|
|
1181
1638
|
}
|
|
1182
1639
|
}
|
|
1183
1640
|
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
|
|
1184
1641
|
prop.type = prop.customType.name;
|
|
1185
1642
|
}
|
|
1186
|
-
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) &&
|
|
1643
|
+
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && !prop.polymorphic && prop.targetMeta.compositePK) {
|
|
1187
1644
|
prop.customTypes = [];
|
|
1188
|
-
for (const pk of
|
|
1645
|
+
for (const pk of prop.targetMeta.getPrimaryProps()) {
|
|
1189
1646
|
if (pk.customType) {
|
|
1190
1647
|
prop.customTypes.push(pk.customType);
|
|
1191
1648
|
prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
@@ -1197,7 +1654,7 @@ export class MetadataDiscovery {
|
|
|
1197
1654
|
}
|
|
1198
1655
|
}
|
|
1199
1656
|
}
|
|
1200
|
-
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof
|
|
1657
|
+
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
|
|
1201
1658
|
if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
|
|
1202
1659
|
prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
|
|
1203
1660
|
}
|
|
@@ -1212,22 +1669,36 @@ export class MetadataDiscovery {
|
|
|
1212
1669
|
}
|
|
1213
1670
|
}
|
|
1214
1671
|
initRelation(prop) {
|
|
1215
|
-
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1672
|
+
if (prop.kind === ReferenceKind.SCALAR || prop.polymorphTargets) {
|
|
1216
1673
|
return;
|
|
1217
1674
|
}
|
|
1218
|
-
|
|
1219
|
-
prop.
|
|
1675
|
+
// when the target is a polymorphic embedded entity, `prop.target` is an array of classes, we need to get the metadata by the type name instead
|
|
1676
|
+
const meta2 = this.metadata.find(prop.target) ?? this.metadata.getByClassName(prop.type);
|
|
1677
|
+
// If targetKey is specified, use that property instead of PKs for referencedPKs
|
|
1678
|
+
prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
|
|
1220
1679
|
prop.targetMeta = meta2;
|
|
1680
|
+
if (meta2.view) {
|
|
1681
|
+
prop.createForeignKeyConstraint = false;
|
|
1682
|
+
}
|
|
1683
|
+
// Auto-generate formula for persist: false relations, but only for single-column FKs
|
|
1684
|
+
// Composite FK relations need standard JOIN conditions, not formula-based
|
|
1221
1685
|
if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
|
|
1222
|
-
|
|
1686
|
+
this.initFieldName(prop);
|
|
1687
|
+
if (prop.fieldNames?.length === 1) {
|
|
1688
|
+
prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
|
|
1689
|
+
}
|
|
1223
1690
|
}
|
|
1224
1691
|
}
|
|
1225
1692
|
initColumnType(prop) {
|
|
1226
1693
|
this.initUnsigned(prop);
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
prop.
|
|
1230
|
-
|
|
1694
|
+
// Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
|
|
1695
|
+
const targetProps = prop.targetMeta
|
|
1696
|
+
? (prop.targetKey ? [prop.targetMeta.properties[prop.targetKey]] : prop.targetMeta.getPrimaryProps())
|
|
1697
|
+
: [];
|
|
1698
|
+
targetProps.map(targetProp => {
|
|
1699
|
+
prop.length ??= targetProp.length;
|
|
1700
|
+
prop.precision ??= targetProp.precision;
|
|
1701
|
+
prop.scale ??= targetProp.scale;
|
|
1231
1702
|
});
|
|
1232
1703
|
if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
|
|
1233
1704
|
delete prop.type;
|
|
@@ -1240,8 +1711,7 @@ export class MetadataDiscovery {
|
|
|
1240
1711
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1241
1712
|
const mappedType = this.getMappedType(prop);
|
|
1242
1713
|
const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
|
|
1243
|
-
if (mappedType instanceof
|
|
1244
|
-
&& !prop.columnTypes
|
|
1714
|
+
if (mappedType instanceof t.unknown
|
|
1245
1715
|
// it could be a runtime type from reflect-metadata
|
|
1246
1716
|
&& !SCALAR_TYPES.includes(prop.type)
|
|
1247
1717
|
// or it might be inferred via ts-morph to some generic type alias
|
|
@@ -1254,23 +1724,31 @@ export class MetadataDiscovery {
|
|
|
1254
1724
|
}
|
|
1255
1725
|
return;
|
|
1256
1726
|
}
|
|
1257
|
-
|
|
1727
|
+
/* v8 ignore next */
|
|
1728
|
+
if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
|
|
1258
1729
|
prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
|
|
1259
1730
|
return;
|
|
1260
1731
|
}
|
|
1261
|
-
const targetMeta =
|
|
1732
|
+
const targetMeta = prop.targetMeta;
|
|
1262
1733
|
prop.columnTypes = [];
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1734
|
+
// Use targetKey property if specified, otherwise use primary key properties
|
|
1735
|
+
const referencedProps = prop.targetKey
|
|
1736
|
+
? [targetMeta.properties[prop.targetKey]]
|
|
1737
|
+
: targetMeta.getPrimaryProps();
|
|
1738
|
+
if (prop.polymorphic && prop.polymorphTargets) {
|
|
1739
|
+
prop.columnTypes.push(this.platform.getVarcharTypeDeclarationSQL(prop));
|
|
1740
|
+
}
|
|
1741
|
+
for (const referencedProp of referencedProps) {
|
|
1742
|
+
this.initCustomType(targetMeta, referencedProp);
|
|
1743
|
+
this.initColumnType(referencedProp);
|
|
1744
|
+
const mappedType = this.getMappedType(referencedProp);
|
|
1745
|
+
let columnTypes = referencedProp.columnTypes;
|
|
1746
|
+
if (referencedProp.autoincrement) {
|
|
1747
|
+
columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.platform)];
|
|
1270
1748
|
}
|
|
1271
1749
|
prop.columnTypes.push(...columnTypes);
|
|
1272
|
-
if (!targetMeta.compositePK) {
|
|
1273
|
-
prop.customType =
|
|
1750
|
+
if (!targetMeta.compositePK || prop.targetKey) {
|
|
1751
|
+
prop.customType = referencedProp.customType;
|
|
1274
1752
|
}
|
|
1275
1753
|
}
|
|
1276
1754
|
}
|
|
@@ -1284,7 +1762,7 @@ export class MetadataDiscovery {
|
|
|
1284
1762
|
t = 'enum';
|
|
1285
1763
|
}
|
|
1286
1764
|
else if (prop.enum) {
|
|
1287
|
-
t = prop.items?.every(item =>
|
|
1765
|
+
t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
|
|
1288
1766
|
}
|
|
1289
1767
|
if (t === 'Date') {
|
|
1290
1768
|
t = 'datetime';
|
|
@@ -1308,8 +1786,7 @@ export class MetadataDiscovery {
|
|
|
1308
1786
|
return;
|
|
1309
1787
|
}
|
|
1310
1788
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
1311
|
-
|
|
1312
|
-
prop.unsigned = meta2.getPrimaryProps().some(pk => {
|
|
1789
|
+
prop.unsigned = prop.targetMeta.getPrimaryProps().some(pk => {
|
|
1313
1790
|
this.initUnsigned(pk);
|
|
1314
1791
|
return pk.unsigned;
|
|
1315
1792
|
});
|
|
@@ -1323,30 +1800,6 @@ export class MetadataDiscovery {
|
|
|
1323
1800
|
prop.index ??= true;
|
|
1324
1801
|
}
|
|
1325
1802
|
}
|
|
1326
|
-
async getEntityClassOrSchema(path, name) {
|
|
1327
|
-
const exports = await Utils.dynamicImport(path);
|
|
1328
|
-
const targets = Object.values(exports)
|
|
1329
|
-
.filter(item => item instanceof EntitySchema || (item instanceof Function && MetadataStorage.isKnownEntity(item.name)));
|
|
1330
|
-
// ignore class implementations that are linked from an EntitySchema
|
|
1331
|
-
for (const item of targets) {
|
|
1332
|
-
if (item instanceof EntitySchema) {
|
|
1333
|
-
targets.forEach((item2, idx) => {
|
|
1334
|
-
if (item.meta.class === item2) {
|
|
1335
|
-
targets.splice(idx, 1);
|
|
1336
|
-
}
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
if (targets.length > 0) {
|
|
1341
|
-
return targets;
|
|
1342
|
-
}
|
|
1343
|
-
const target = exports.default ?? exports[name];
|
|
1344
|
-
/* v8 ignore next 3 */
|
|
1345
|
-
if (!target) {
|
|
1346
|
-
throw MetadataError.entityNotFound(name, path.replace(this.config.get('baseDir'), '.'));
|
|
1347
|
-
}
|
|
1348
|
-
return [target];
|
|
1349
|
-
}
|
|
1350
1803
|
shouldForceConstructorUsage(meta) {
|
|
1351
1804
|
const forceConstructor = this.config.get('forceEntityConstructor');
|
|
1352
1805
|
if (Array.isArray(forceConstructor)) {
|