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