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