@mikro-orm/sql 7.0.15 → 7.0.16-dev.0
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/AbstractSqlConnection.d.ts +58 -94
- package/AbstractSqlConnection.js +238 -235
- package/AbstractSqlDriver.d.ts +155 -410
- package/AbstractSqlDriver.js +1972 -2100
- package/AbstractSqlPlatform.d.ts +76 -86
- package/AbstractSqlPlatform.js +167 -169
- package/PivotCollectionPersister.d.ts +15 -33
- package/PivotCollectionPersister.js +160 -158
- package/README.md +1 -1
- package/SqlEntityManager.d.ts +22 -67
- package/SqlEntityManager.js +38 -54
- package/SqlEntityRepository.d.ts +14 -14
- package/SqlEntityRepository.js +23 -23
- package/SqlMikroORM.d.ts +8 -49
- package/SqlMikroORM.js +8 -8
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +12 -12
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +201 -199
- package/dialects/mysql/BaseMySqlPlatform.d.ts +46 -65
- package/dialects/mysql/BaseMySqlPlatform.js +134 -137
- package/dialects/mysql/MySqlExceptionConverter.d.ts +6 -6
- package/dialects/mysql/MySqlExceptionConverter.js +77 -91
- package/dialects/mysql/MySqlNativeQueryBuilder.d.ts +3 -3
- package/dialects/mysql/MySqlNativeQueryBuilder.js +69 -66
- package/dialects/mysql/MySqlSchemaHelper.d.ts +39 -58
- package/dialects/mysql/MySqlSchemaHelper.js +319 -327
- package/dialects/oracledb/OracleDialect.d.ts +52 -81
- package/dialects/oracledb/OracleDialect.js +149 -155
- package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +12 -12
- package/dialects/oracledb/OracleNativeQueryBuilder.js +243 -239
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +107 -110
- package/dialects/postgresql/BasePostgreSqlPlatform.js +369 -370
- package/dialects/postgresql/FullTextType.d.ts +6 -10
- package/dialects/postgresql/FullTextType.js +51 -51
- package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +5 -5
- package/dialects/postgresql/PostgreSqlExceptionConverter.js +43 -55
- package/dialects/postgresql/PostgreSqlNativeQueryBuilder.d.ts +1 -1
- package/dialects/postgresql/PostgreSqlNativeQueryBuilder.js +4 -4
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +82 -117
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +712 -748
- package/dialects/sqlite/BaseSqliteConnection.d.ts +5 -3
- package/dialects/sqlite/BaseSqliteConnection.js +19 -21
- package/dialects/sqlite/NodeSqliteDialect.d.ts +1 -1
- package/dialects/sqlite/NodeSqliteDialect.js +23 -23
- package/dialects/sqlite/SqliteDriver.d.ts +1 -1
- package/dialects/sqlite/SqliteDriver.js +3 -3
- package/dialects/sqlite/SqliteExceptionConverter.d.ts +6 -6
- package/dialects/sqlite/SqliteExceptionConverter.js +51 -67
- package/dialects/sqlite/SqliteNativeQueryBuilder.d.ts +2 -2
- package/dialects/sqlite/SqliteNativeQueryBuilder.js +7 -7
- package/dialects/sqlite/SqlitePlatform.d.ts +73 -64
- package/dialects/sqlite/SqlitePlatform.js +143 -143
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +61 -78
- package/dialects/sqlite/SqliteSchemaHelper.js +522 -541
- package/package.json +2 -2
- package/plugin/index.d.ts +35 -42
- package/plugin/index.js +36 -43
- package/plugin/transformer.d.ts +102 -136
- package/plugin/transformer.js +988 -1010
- package/query/ArrayCriteriaNode.d.ts +4 -4
- package/query/ArrayCriteriaNode.js +18 -18
- package/query/CriteriaNode.d.ts +25 -35
- package/query/CriteriaNode.js +132 -142
- package/query/CriteriaNodeFactory.d.ts +6 -49
- package/query/CriteriaNodeFactory.js +94 -97
- package/query/NativeQueryBuilder.d.ts +120 -120
- package/query/NativeQueryBuilder.js +501 -507
- package/query/ObjectCriteriaNode.d.ts +12 -12
- package/query/ObjectCriteriaNode.js +282 -298
- package/query/QueryBuilder.d.ts +906 -1558
- package/query/QueryBuilder.js +2217 -2346
- package/query/QueryBuilderHelper.d.ts +72 -153
- package/query/QueryBuilderHelper.js +1032 -1084
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +46 -53
- package/query/enums.d.ts +14 -14
- package/query/enums.js +14 -14
- package/query/raw.d.ts +6 -16
- package/query/raw.js +10 -10
- package/schema/DatabaseSchema.d.ts +50 -74
- package/schema/DatabaseSchema.js +331 -359
- package/schema/DatabaseTable.d.ts +73 -96
- package/schema/DatabaseTable.js +974 -1046
- package/schema/SchemaComparator.d.ts +66 -70
- package/schema/SchemaComparator.js +765 -790
- package/schema/SchemaHelper.d.ts +97 -128
- package/schema/SchemaHelper.js +668 -683
- package/schema/SqlSchemaGenerator.d.ts +59 -79
- package/schema/SqlSchemaGenerator.js +495 -525
- package/typings.d.ts +275 -405
package/schema/DatabaseTable.js
CHANGED
|
@@ -1,1101 +1,1029 @@
|
|
|
1
|
-
import { DecimalType, EntitySchema, isRaw, ReferenceKind, t, Type, UnknownType, Utils } from '@mikro-orm/core';
|
|
1
|
+
import { DecimalType, EntitySchema, isRaw, ReferenceKind, t, Type, UnknownType, Utils, } from '@mikro-orm/core';
|
|
2
2
|
/**
|
|
3
3
|
* @internal
|
|
4
4
|
*/
|
|
5
5
|
export class DatabaseTable {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
getQuotedName() {
|
|
21
|
-
return this.#platform.quoteIdentifier(this.getShortestName());
|
|
22
|
-
}
|
|
23
|
-
getColumns() {
|
|
24
|
-
return Object.values(this.#columns);
|
|
25
|
-
}
|
|
26
|
-
getColumn(name) {
|
|
27
|
-
return this.#columns[name];
|
|
28
|
-
}
|
|
29
|
-
removeColumn(name) {
|
|
30
|
-
delete this.#columns[name];
|
|
31
|
-
}
|
|
32
|
-
getIndexes() {
|
|
33
|
-
return Utils.removeDuplicates(this.#indexes);
|
|
34
|
-
}
|
|
35
|
-
getChecks() {
|
|
36
|
-
return this.#checks;
|
|
37
|
-
}
|
|
38
|
-
/** @internal */
|
|
39
|
-
setIndexes(indexes) {
|
|
40
|
-
this.#indexes = indexes;
|
|
41
|
-
}
|
|
42
|
-
/** @internal */
|
|
43
|
-
setChecks(checks) {
|
|
44
|
-
this.#checks = checks;
|
|
45
|
-
}
|
|
46
|
-
/** @internal */
|
|
47
|
-
setForeignKeys(fks) {
|
|
48
|
-
this.#foreignKeys = fks;
|
|
49
|
-
}
|
|
50
|
-
init(cols, indexes = [], checks = [], pks, fks = {}, enums = {}) {
|
|
51
|
-
this.#indexes = indexes;
|
|
52
|
-
this.#checks = checks;
|
|
53
|
-
this.#foreignKeys = fks;
|
|
54
|
-
const helper = this.#platform.getSchemaHelper();
|
|
55
|
-
this.#columns = cols.reduce((o, v) => {
|
|
56
|
-
const index = indexes.filter(i => i.columnNames[0] === v.name);
|
|
57
|
-
v.primary = v.primary || pks.includes(v.name);
|
|
58
|
-
v.unique = index.some(i => i.unique && !i.primary);
|
|
59
|
-
const type = v.name in enums ? 'enum' : v.type;
|
|
60
|
-
v.mappedType = this.#platform.getMappedType(type);
|
|
61
|
-
v.default = v.default?.toString().startsWith('nextval(') ? null : v.default;
|
|
62
|
-
v.enumItems ??= enums[v.name] || [];
|
|
63
|
-
// recover length from the declared type so introspection matches `addColumnFromProperty`;
|
|
64
|
-
// scoped to types with `getDefaultLength` to skip mysql's `tinyint(1)` boolean width
|
|
65
|
-
if (v.length == null && v.type && helper && typeof v.mappedType.getDefaultLength !== 'undefined') {
|
|
66
|
-
v.length = helper.inferLengthFromColumnType(v.type);
|
|
67
|
-
}
|
|
68
|
-
o[v.name] = v;
|
|
69
|
-
return o;
|
|
70
|
-
}, {});
|
|
71
|
-
}
|
|
72
|
-
addColumn(column) {
|
|
73
|
-
this.#columns[column.name] = column;
|
|
74
|
-
}
|
|
75
|
-
addColumnFromProperty(prop, meta, config) {
|
|
76
|
-
prop.fieldNames?.forEach((field, idx) => {
|
|
77
|
-
// numeric enums fall through to the underlying numeric type — no platform emits a CHECK we could parse back
|
|
78
|
-
const isStringEnum = !!prop.nativeEnumName || !!prop.items?.every(item => typeof item === 'string');
|
|
79
|
-
const type = prop.enum && isStringEnum ? 'enum' : prop.columnTypes[idx];
|
|
80
|
-
const mappedType = this.#platform.getMappedType(type);
|
|
81
|
-
if (mappedType instanceof DecimalType) {
|
|
82
|
-
const match = /\w+\((\d+), ?(\d+)\)/.exec(prop.columnTypes[idx]);
|
|
83
|
-
/* v8 ignore next */
|
|
84
|
-
if (match) {
|
|
85
|
-
prop.precision ??= +match[1];
|
|
86
|
-
prop.scale ??= +match[2];
|
|
87
|
-
prop.length = undefined;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (prop.length == null && prop.columnTypes[idx]) {
|
|
91
|
-
prop.length = this.#platform.getSchemaHelper().inferLengthFromColumnType(prop.columnTypes[idx]);
|
|
92
|
-
if (typeof mappedType.getDefaultLength !== 'undefined') {
|
|
93
|
-
prop.length ??= mappedType.getDefaultLength(this.#platform);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const primary = !meta.compositePK && prop.fieldNames.length === 1 && !!prop.primary;
|
|
97
|
-
this.#columns[field] = {
|
|
98
|
-
name: prop.fieldNames[idx],
|
|
99
|
-
type: prop.columnTypes[idx],
|
|
100
|
-
generated: isRaw(prop.generated)
|
|
101
|
-
? this.#platform.formatQuery(prop.generated.sql, prop.generated.params)
|
|
102
|
-
: prop.generated,
|
|
103
|
-
mappedType,
|
|
104
|
-
unsigned: prop.unsigned && this.#platform.isNumericColumn(mappedType),
|
|
105
|
-
autoincrement:
|
|
106
|
-
prop.autoincrement ??
|
|
107
|
-
(primary && prop.kind === ReferenceKind.SCALAR && this.#platform.isNumericColumn(mappedType)),
|
|
108
|
-
primary,
|
|
109
|
-
nullable: this.#columns[field]?.nullable ?? !!prop.nullable,
|
|
110
|
-
nativeEnumName: prop.nativeEnumName,
|
|
111
|
-
length: prop.length,
|
|
112
|
-
precision: prop.precision,
|
|
113
|
-
scale: prop.scale,
|
|
114
|
-
default: prop.defaultRaw,
|
|
115
|
-
enumItems: prop.nativeEnumName || prop.items?.every(i => typeof i === 'string') ? prop.items : undefined,
|
|
116
|
-
comment: prop.comment,
|
|
117
|
-
extra: prop.extra,
|
|
118
|
-
ignoreSchemaChanges: prop.ignoreSchemaChanges,
|
|
119
|
-
};
|
|
120
|
-
this.#columns[field].unsigned ??= this.#columns[field].autoincrement;
|
|
121
|
-
if (this.nativeEnums[type]) {
|
|
122
|
-
this.#columns[field].enumItems ??= this.nativeEnums[type].items;
|
|
123
|
-
}
|
|
124
|
-
const defaultValue = this.#platform.getSchemaHelper().normalizeDefaultValue(prop.defaultRaw, prop.length);
|
|
125
|
-
this.#columns[field].default = defaultValue;
|
|
126
|
-
});
|
|
127
|
-
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
|
|
128
|
-
const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
|
|
129
|
-
let schema =
|
|
130
|
-
prop.targetMeta.root.schema === '*'
|
|
131
|
-
? this.schema
|
|
132
|
-
: (prop.targetMeta.root.schema ?? config.get('schema', this.#platform.getDefaultSchemaName()));
|
|
133
|
-
if (prop.referencedTableName.includes('.')) {
|
|
134
|
-
schema = undefined;
|
|
135
|
-
}
|
|
136
|
-
// For cross-schema FKs on MySQL/MariaDB (where schema = database), when the referenced
|
|
137
|
-
// table has no explicit schema but the current table does, qualify with dbName so the
|
|
138
|
-
// FK can resolve the referenced table in the correct database
|
|
139
|
-
if (!schema && this.schema && !this.#platform.getDefaultSchemaName()) {
|
|
140
|
-
schema = config.get('dbName');
|
|
141
|
-
}
|
|
142
|
-
if (prop.createForeignKeyConstraint) {
|
|
143
|
-
this.#foreignKeys[constraintName] = {
|
|
144
|
-
constraintName,
|
|
145
|
-
columnNames: prop.fieldNames,
|
|
146
|
-
localTableName: this.getShortestName(false),
|
|
147
|
-
referencedColumnNames: prop.referencedColumnNames,
|
|
148
|
-
referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
|
|
149
|
-
};
|
|
150
|
-
const schemaConfig = config.get('schemaGenerator');
|
|
151
|
-
this.#foreignKeys[constraintName].deleteRule = prop.deleteRule ?? schemaConfig.defaultDeleteRule;
|
|
152
|
-
this.#foreignKeys[constraintName].updateRule = prop.updateRule ?? schemaConfig.defaultUpdateRule;
|
|
153
|
-
if (prop.deferMode) {
|
|
154
|
-
this.#foreignKeys[constraintName].deferMode = prop.deferMode;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
6
|
+
name;
|
|
7
|
+
schema;
|
|
8
|
+
#columns = {};
|
|
9
|
+
#indexes = [];
|
|
10
|
+
#checks = [];
|
|
11
|
+
#foreignKeys = {};
|
|
12
|
+
#platform;
|
|
13
|
+
nativeEnums = {}; // for postgres
|
|
14
|
+
comment;
|
|
15
|
+
constructor(platform, name, schema) {
|
|
16
|
+
this.name = name;
|
|
17
|
+
this.schema = schema;
|
|
18
|
+
this.#platform = platform;
|
|
157
19
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
columnNames: prop.fieldNames,
|
|
161
|
-
composite: prop.fieldNames.length > 1,
|
|
162
|
-
keyName: this.getIndexName(prop.index, prop.fieldNames, 'index'),
|
|
163
|
-
constraint: false,
|
|
164
|
-
primary: false,
|
|
165
|
-
unique: false,
|
|
166
|
-
});
|
|
20
|
+
getQuotedName() {
|
|
21
|
+
return this.#platform.quoteIdentifier(this.getShortestName());
|
|
167
22
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
columnNames: prop.fieldNames,
|
|
171
|
-
composite: prop.fieldNames.length > 1,
|
|
172
|
-
keyName: this.getIndexName(prop.unique, prop.fieldNames, 'unique'),
|
|
173
|
-
constraint: !prop.fieldNames.some(d => d.includes('.')),
|
|
174
|
-
primary: false,
|
|
175
|
-
unique: true,
|
|
176
|
-
deferMode: prop.deferMode,
|
|
177
|
-
});
|
|
23
|
+
getColumns() {
|
|
24
|
+
return Object.values(this.#columns);
|
|
178
25
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (typeof value === 'string') {
|
|
182
|
-
return value;
|
|
26
|
+
getColumn(name) {
|
|
27
|
+
return this.#columns[name];
|
|
183
28
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
index.expression ||
|
|
201
|
-
!(index.columnNames[0] in columnFks)) && // Trivial non-composite indexes for scalar props are to be mapped to the column.
|
|
202
|
-
// ignore indexes that don't have all column names (this can happen in sqlite where there is no way to infer this for expressions)
|
|
203
|
-
!(index.columnNames.some(col => !col) && !index.expression),
|
|
204
|
-
);
|
|
205
|
-
// Helper to map column name to property name
|
|
206
|
-
const columnToPropertyName = colName => this.getPropertyName(namingStrategy, colName);
|
|
207
|
-
for (const index of potentiallyUnmappedIndexes) {
|
|
208
|
-
// Build the index/unique options object with advanced options
|
|
209
|
-
const ret = {
|
|
210
|
-
name: index.keyName,
|
|
211
|
-
deferMode: index.deferMode,
|
|
212
|
-
expression: index.expression,
|
|
213
|
-
// Advanced index options - convert column names to property names
|
|
214
|
-
columns: index.columns?.map(col => ({
|
|
215
|
-
...col,
|
|
216
|
-
name: columnToPropertyName(col.name),
|
|
217
|
-
})),
|
|
218
|
-
include: index.include?.map(colName => columnToPropertyName(colName)),
|
|
219
|
-
fillFactor: index.fillFactor,
|
|
220
|
-
disabled: index.disabled,
|
|
221
|
-
};
|
|
222
|
-
// Index-only options (not valid for Unique)
|
|
223
|
-
if (!index.unique) {
|
|
224
|
-
if (index.type) {
|
|
225
|
-
// Convert index type - IndexDef.type can be string or object, IndexOptions.type is just string
|
|
226
|
-
ret.type = typeof index.type === 'string' ? index.type : index.type.indexType;
|
|
227
|
-
}
|
|
228
|
-
if (index.invisible) {
|
|
229
|
-
ret.invisible = index.invisible;
|
|
230
|
-
}
|
|
231
|
-
if (index.clustered) {
|
|
232
|
-
ret.clustered = index.clustered;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// An index is trivial if it has no special options that require entity-level declaration
|
|
236
|
-
const hasAdvancedOptions =
|
|
237
|
-
index.columns?.length ||
|
|
238
|
-
index.include?.length ||
|
|
239
|
-
index.fillFactor ||
|
|
240
|
-
index.type ||
|
|
241
|
-
index.invisible ||
|
|
242
|
-
index.disabled ||
|
|
243
|
-
index.clustered;
|
|
244
|
-
const isTrivial = !index.deferMode && !index.expression && !hasAdvancedOptions;
|
|
245
|
-
if (isTrivial) {
|
|
246
|
-
// Index is for FK. Map to the FK prop and move on.
|
|
247
|
-
const fkForIndex = fkIndexes.get(index);
|
|
248
|
-
if (fkForIndex && !fkForIndex.fk.columnNames.some(col => !index.columnNames.includes(col))) {
|
|
249
|
-
ret.properties = [this.getPropertyName(namingStrategy, fkForIndex.baseName, fkForIndex.fk)];
|
|
250
|
-
const map = index.unique ? compositeFkUniques : compositeFkIndexes;
|
|
251
|
-
if (typeof map[ret.properties[0]] === 'undefined') {
|
|
252
|
-
map[ret.properties[0]] = index;
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
const properties =
|
|
258
|
-
ret.properties ??
|
|
259
|
-
this.getIndexProperties(index, columnFks, fksOnColumnProps, fksOnStandaloneProps, namingStrategy);
|
|
260
|
-
// If there is a column that cannot be unambiguously mapped to a prop, render an expression.
|
|
261
|
-
if (typeof properties === 'undefined') {
|
|
262
|
-
ret.expression ??= schemaHelper.getCreateIndexSQL(this.name, index);
|
|
263
|
-
} else {
|
|
264
|
-
ret.properties ??= properties;
|
|
265
|
-
// If the index is for one property that is not a FK prop, map to the column prop and move on.
|
|
266
|
-
if (properties.length === 1 && isTrivial && !fksOnStandaloneProps.has(properties[0])) {
|
|
267
|
-
const map = index.unique ? compositeFkUniques : compositeFkIndexes;
|
|
268
|
-
// Only map one trivial index. If the same column is indexed many times over, output
|
|
269
|
-
if (typeof map[properties[0]] === 'undefined') {
|
|
270
|
-
map[properties[0]] = index;
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
// Composite indexes that aren't exclusively mapped to FK props get an entity decorator.
|
|
276
|
-
if (index.unique) {
|
|
277
|
-
schema.addUnique(ret);
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
schema.addIndex(ret);
|
|
29
|
+
removeColumn(name) {
|
|
30
|
+
delete this.#columns[name];
|
|
31
|
+
}
|
|
32
|
+
getIndexes() {
|
|
33
|
+
return Utils.removeDuplicates(this.#indexes);
|
|
34
|
+
}
|
|
35
|
+
getChecks() {
|
|
36
|
+
return this.#checks;
|
|
37
|
+
}
|
|
38
|
+
/** @internal */
|
|
39
|
+
setIndexes(indexes) {
|
|
40
|
+
this.#indexes = indexes;
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
setChecks(checks) {
|
|
44
|
+
this.#checks = checks;
|
|
281
45
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const columnName = column.name;
|
|
286
|
-
const standaloneFkPropBasedOnColumn = fksOnStandaloneProps.get(columnName);
|
|
287
|
-
if (standaloneFkPropBasedOnColumn && !fksOnColumnProps.get(columnName)) {
|
|
288
|
-
addedStandaloneFkPropsBasedOnColumn.add(columnName);
|
|
289
|
-
const { fkIndex, currentFk } = standaloneFkPropBasedOnColumn;
|
|
290
|
-
const prop = this.getForeignKeyDeclaration(
|
|
291
|
-
currentFk,
|
|
292
|
-
namingStrategy,
|
|
293
|
-
schemaHelper,
|
|
294
|
-
fkIndex,
|
|
295
|
-
nullableForeignKeys.has(currentFk),
|
|
296
|
-
columnName,
|
|
297
|
-
fksOnColumnProps,
|
|
298
|
-
);
|
|
299
|
-
schema.addProperty(prop.name, prop.type, prop);
|
|
300
|
-
}
|
|
301
|
-
const prop = this.getPropertyDeclaration(
|
|
302
|
-
column,
|
|
303
|
-
namingStrategy,
|
|
304
|
-
schemaHelper,
|
|
305
|
-
compositeFkIndexes,
|
|
306
|
-
compositeFkUniques,
|
|
307
|
-
columnFks,
|
|
308
|
-
fksOnColumnProps.get(columnName),
|
|
309
|
-
);
|
|
310
|
-
schema.addProperty(prop.name, prop.type, prop);
|
|
46
|
+
/** @internal */
|
|
47
|
+
setForeignKeys(fks) {
|
|
48
|
+
this.#foreignKeys = fks;
|
|
311
49
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
50
|
+
init(cols, indexes = [], checks = [], pks, fks = {}, enums = {}) {
|
|
51
|
+
this.#indexes = indexes;
|
|
52
|
+
this.#checks = checks;
|
|
53
|
+
this.#foreignKeys = fks;
|
|
54
|
+
const helper = this.#platform.getSchemaHelper();
|
|
55
|
+
this.#columns = cols.reduce((o, v) => {
|
|
56
|
+
const index = indexes.filter(i => i.columnNames[0] === v.name);
|
|
57
|
+
v.primary = v.primary || pks.includes(v.name);
|
|
58
|
+
v.unique = index.some(i => i.unique && !i.primary);
|
|
59
|
+
const type = v.name in enums ? 'enum' : v.type;
|
|
60
|
+
v.mappedType = this.#platform.getMappedType(type);
|
|
61
|
+
v.default = v.default?.toString().startsWith('nextval(') ? null : v.default;
|
|
62
|
+
v.enumItems ??= enums[v.name] || [];
|
|
63
|
+
// recover length from the declared type so introspection matches `addColumnFromProperty`;
|
|
64
|
+
// scoped to types with `getDefaultLength` to skip mysql's `tinyint(1)` boolean width
|
|
65
|
+
if (v.length == null && v.type && helper && typeof v.mappedType.getDefaultLength !== 'undefined') {
|
|
66
|
+
v.length = helper.inferLengthFromColumnType(v.type);
|
|
67
|
+
}
|
|
68
|
+
o[v.name] = v;
|
|
69
|
+
return o;
|
|
70
|
+
}, {});
|
|
326
71
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
prop => prop.primary && prop.kind === ReferenceKind.MANY_TO_ONE,
|
|
330
|
-
);
|
|
331
|
-
if (
|
|
332
|
-
oneToOneCandidateProperties.length === 1 &&
|
|
333
|
-
oneToOneCandidateProperties[0].fieldNames.length ===
|
|
334
|
-
new Set(meta.getPrimaryProps().flatMap(prop => prop.fieldNames)).size
|
|
335
|
-
) {
|
|
336
|
-
oneToOneCandidateProperties[0].kind = ReferenceKind.ONE_TO_ONE;
|
|
72
|
+
addColumn(column) {
|
|
73
|
+
this.#columns[column.name] = column;
|
|
337
74
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
75
|
+
addColumnFromProperty(prop, meta, config) {
|
|
76
|
+
prop.fieldNames?.forEach((field, idx) => {
|
|
77
|
+
// numeric enums fall through to the underlying numeric type — no platform emits a CHECK we could parse back
|
|
78
|
+
const isStringEnum = !!prop.nativeEnumName || !!prop.items?.every(item => typeof item === 'string');
|
|
79
|
+
const type = prop.enum && isStringEnum ? 'enum' : prop.columnTypes[idx];
|
|
80
|
+
const mappedType = this.#platform.getMappedType(type);
|
|
81
|
+
if (mappedType instanceof DecimalType) {
|
|
82
|
+
const match = /\w+\((\d+), ?(\d+)\)/.exec(prop.columnTypes[idx]);
|
|
83
|
+
/* v8 ignore next */
|
|
84
|
+
if (match) {
|
|
85
|
+
prop.precision ??= +match[1];
|
|
86
|
+
prop.scale ??= +match[2];
|
|
87
|
+
prop.length = undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (prop.length == null && prop.columnTypes[idx]) {
|
|
91
|
+
prop.length = this.#platform.getSchemaHelper().inferLengthFromColumnType(prop.columnTypes[idx]);
|
|
92
|
+
if (typeof mappedType.getDefaultLength !== 'undefined') {
|
|
93
|
+
prop.length ??= mappedType.getDefaultLength(this.#platform);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const primary = !meta.compositePK && prop.fieldNames.length === 1 && !!prop.primary;
|
|
97
|
+
this.#columns[field] = {
|
|
98
|
+
name: prop.fieldNames[idx],
|
|
99
|
+
type: prop.columnTypes[idx],
|
|
100
|
+
generated: isRaw(prop.generated)
|
|
101
|
+
? this.#platform.formatQuery(prop.generated.sql, prop.generated.params)
|
|
102
|
+
: prop.generated,
|
|
103
|
+
mappedType,
|
|
104
|
+
unsigned: prop.unsigned && this.#platform.isNumericColumn(mappedType),
|
|
105
|
+
autoincrement: prop.autoincrement ??
|
|
106
|
+
(primary && prop.kind === ReferenceKind.SCALAR && this.#platform.isNumericColumn(mappedType)),
|
|
107
|
+
primary,
|
|
108
|
+
nullable: this.#columns[field]?.nullable ?? !!prop.nullable,
|
|
109
|
+
nativeEnumName: prop.nativeEnumName,
|
|
110
|
+
length: prop.length,
|
|
111
|
+
precision: prop.precision,
|
|
112
|
+
scale: prop.scale,
|
|
113
|
+
default: prop.defaultRaw,
|
|
114
|
+
enumItems: prop.nativeEnumName || prop.items?.every(i => typeof i === 'string') ? prop.items : undefined,
|
|
115
|
+
comment: prop.comment,
|
|
116
|
+
extra: prop.extra,
|
|
117
|
+
ignoreSchemaChanges: prop.ignoreSchemaChanges,
|
|
118
|
+
};
|
|
119
|
+
this.#columns[field].unsigned ??= this.#columns[field].autoincrement;
|
|
120
|
+
if (this.nativeEnums[type]) {
|
|
121
|
+
this.#columns[field].enumItems ??= this.nativeEnums[type].items;
|
|
122
|
+
}
|
|
123
|
+
const defaultValue = this.#platform.getSchemaHelper().normalizeDefaultValue(prop.defaultRaw, prop.length);
|
|
124
|
+
this.#columns[field].default = defaultValue;
|
|
125
|
+
});
|
|
126
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
|
|
127
|
+
const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
|
|
128
|
+
let schema = prop.targetMeta.root.schema === '*'
|
|
129
|
+
? this.schema
|
|
130
|
+
: (prop.targetMeta.root.schema ?? config.get('schema', this.#platform.getDefaultSchemaName()));
|
|
131
|
+
if (prop.referencedTableName.includes('.')) {
|
|
132
|
+
schema = undefined;
|
|
133
|
+
}
|
|
134
|
+
// For cross-schema FKs on MySQL/MariaDB (where schema = database), when the referenced
|
|
135
|
+
// table has no explicit schema but the current table does, qualify with dbName so the
|
|
136
|
+
// FK can resolve the referenced table in the correct database
|
|
137
|
+
if (!schema && this.schema && !this.#platform.getDefaultSchemaName()) {
|
|
138
|
+
schema = config.get('dbName');
|
|
139
|
+
}
|
|
140
|
+
if (prop.createForeignKeyConstraint) {
|
|
141
|
+
this.#foreignKeys[constraintName] = {
|
|
142
|
+
constraintName,
|
|
143
|
+
columnNames: prop.fieldNames,
|
|
144
|
+
localTableName: this.getShortestName(false),
|
|
145
|
+
referencedColumnNames: prop.referencedColumnNames,
|
|
146
|
+
referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
|
|
147
|
+
};
|
|
148
|
+
const schemaConfig = config.get('schemaGenerator');
|
|
149
|
+
this.#foreignKeys[constraintName].deleteRule = prop.deleteRule ?? schemaConfig.defaultDeleteRule;
|
|
150
|
+
this.#foreignKeys[constraintName].updateRule = prop.updateRule ?? schemaConfig.defaultUpdateRule;
|
|
151
|
+
if (prop.deferMode) {
|
|
152
|
+
this.#foreignKeys[constraintName].deferMode = prop.deferMode;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
362
155
|
}
|
|
363
|
-
if (
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (fkIndex) {
|
|
373
|
-
fkIndexes.set(fkIndex, { fk: currentFk, baseName: columnName });
|
|
374
|
-
}
|
|
156
|
+
if (prop.index) {
|
|
157
|
+
this.#indexes.push({
|
|
158
|
+
columnNames: prop.fieldNames,
|
|
159
|
+
composite: prop.fieldNames.length > 1,
|
|
160
|
+
keyName: this.getIndexName(prop.index, prop.fieldNames, 'index'),
|
|
161
|
+
constraint: false,
|
|
162
|
+
primary: false,
|
|
163
|
+
unique: false,
|
|
164
|
+
});
|
|
375
165
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
166
|
+
if (prop.unique && !(prop.primary && !meta.compositePK)) {
|
|
167
|
+
this.#indexes.push({
|
|
168
|
+
columnNames: prop.fieldNames,
|
|
169
|
+
composite: prop.fieldNames.length > 1,
|
|
170
|
+
keyName: this.getIndexName(prop.unique, prop.fieldNames, 'unique'),
|
|
171
|
+
constraint: !prop.fieldNames.some((d) => d.includes('.')),
|
|
172
|
+
primary: false,
|
|
173
|
+
unique: true,
|
|
174
|
+
deferMode: prop.deferMode,
|
|
175
|
+
});
|
|
385
176
|
}
|
|
386
|
-
|
|
387
|
-
|
|
177
|
+
}
|
|
178
|
+
getIndexName(value, columnNames, type) {
|
|
179
|
+
if (typeof value === 'string') {
|
|
180
|
+
return value;
|
|
388
181
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
182
|
+
return this.#platform.getIndexName(this.name, columnNames, type);
|
|
183
|
+
}
|
|
184
|
+
getEntityDeclaration(namingStrategy, schemaHelper, scalarPropertiesForRelations) {
|
|
185
|
+
const { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames } = this.foreignKeysToProps(namingStrategy, scalarPropertiesForRelations);
|
|
186
|
+
const name = namingStrategy.getEntityName(this.name, this.schema);
|
|
187
|
+
const schema = new EntitySchema({ name, collection: this.name, schema: this.schema, comment: this.comment });
|
|
188
|
+
const compositeFkIndexes = {};
|
|
189
|
+
const compositeFkUniques = {};
|
|
190
|
+
const potentiallyUnmappedIndexes = this.#indexes.filter(index => !index.primary && // Skip primary index. Whether it's in use by scalar column or FK, it's already mapped.
|
|
191
|
+
// Non-trivial non-composite indexes will be declared at the entity's metadata, though later outputted in the property
|
|
192
|
+
(index.columnNames.length > 1 || // All composite indexes are to be mapped to entity decorators or FK props.
|
|
193
|
+
skippedColumnNames.includes(index.columnNames[0]) || // Non-composite indexes for skipped columns are to be mapped as entity decorators.
|
|
194
|
+
index.deferMode ||
|
|
195
|
+
index.expression ||
|
|
196
|
+
!(index.columnNames[0] in columnFks)) && // Trivial non-composite indexes for scalar props are to be mapped to the column.
|
|
197
|
+
// ignore indexes that don't have all column names (this can happen in sqlite where there is no way to infer this for expressions)
|
|
198
|
+
!(index.columnNames.some(col => !col) && !index.expression));
|
|
199
|
+
// Helper to map column name to property name
|
|
200
|
+
const columnToPropertyName = (colName) => this.getPropertyName(namingStrategy, colName);
|
|
201
|
+
for (const index of potentiallyUnmappedIndexes) {
|
|
202
|
+
// Build the index/unique options object with advanced options
|
|
203
|
+
const ret = {
|
|
204
|
+
name: index.keyName,
|
|
205
|
+
deferMode: index.deferMode,
|
|
206
|
+
expression: index.expression,
|
|
207
|
+
// Advanced index options - convert column names to property names
|
|
208
|
+
columns: index.columns?.map(col => ({
|
|
209
|
+
...col,
|
|
210
|
+
name: columnToPropertyName(col.name),
|
|
211
|
+
})),
|
|
212
|
+
include: index.include?.map(colName => columnToPropertyName(colName)),
|
|
213
|
+
fillFactor: index.fillFactor,
|
|
214
|
+
disabled: index.disabled,
|
|
215
|
+
};
|
|
216
|
+
// Index-only options (not valid for Unique)
|
|
217
|
+
if (!index.unique) {
|
|
218
|
+
if (index.type) {
|
|
219
|
+
// Convert index type - IndexDef.type can be string or object, IndexOptions.type is just string
|
|
220
|
+
ret.type = typeof index.type === 'string' ? index.type : index.type.indexType;
|
|
221
|
+
}
|
|
222
|
+
if (index.invisible) {
|
|
223
|
+
ret.invisible = index.invisible;
|
|
224
|
+
}
|
|
225
|
+
if (index.clustered) {
|
|
226
|
+
ret.clustered = index.clustered;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// An index is trivial if it has no special options that require entity-level declaration
|
|
230
|
+
const hasAdvancedOptions = index.columns?.length ||
|
|
231
|
+
index.include?.length ||
|
|
232
|
+
index.fillFactor ||
|
|
233
|
+
index.type ||
|
|
234
|
+
index.invisible ||
|
|
235
|
+
index.disabled ||
|
|
236
|
+
index.clustered;
|
|
237
|
+
const isTrivial = !index.deferMode && !index.expression && !hasAdvancedOptions;
|
|
238
|
+
if (isTrivial) {
|
|
239
|
+
// Index is for FK. Map to the FK prop and move on.
|
|
240
|
+
const fkForIndex = fkIndexes.get(index);
|
|
241
|
+
if (fkForIndex && !fkForIndex.fk.columnNames.some(col => !index.columnNames.includes(col))) {
|
|
242
|
+
ret.properties = [this.getPropertyName(namingStrategy, fkForIndex.baseName, fkForIndex.fk)];
|
|
243
|
+
const map = index.unique ? compositeFkUniques : compositeFkIndexes;
|
|
244
|
+
if (typeof map[ret.properties[0]] === 'undefined') {
|
|
245
|
+
map[ret.properties[0]] = index;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const properties = ret.properties ??
|
|
251
|
+
this.getIndexProperties(index, columnFks, fksOnColumnProps, fksOnStandaloneProps, namingStrategy);
|
|
252
|
+
// If there is a column that cannot be unambiguously mapped to a prop, render an expression.
|
|
253
|
+
if (typeof properties === 'undefined') {
|
|
254
|
+
ret.expression ??= schemaHelper.getCreateIndexSQL(this.name, index);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
ret.properties ??= properties;
|
|
258
|
+
// If the index is for one property that is not a FK prop, map to the column prop and move on.
|
|
259
|
+
if (properties.length === 1 && isTrivial && !fksOnStandaloneProps.has(properties[0])) {
|
|
260
|
+
const map = index.unique ? compositeFkUniques : compositeFkIndexes;
|
|
261
|
+
// Only map one trivial index. If the same column is indexed many times over, output
|
|
262
|
+
if (typeof map[properties[0]] === 'undefined') {
|
|
263
|
+
map[properties[0]] = index;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Composite indexes that aren't exclusively mapped to FK props get an entity decorator.
|
|
269
|
+
if (index.unique) {
|
|
270
|
+
schema.addUnique(ret);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
schema.addIndex(ret);
|
|
274
|
+
}
|
|
275
|
+
const addedStandaloneFkPropsBasedOnColumn = new Set();
|
|
276
|
+
const nonSkippedColumns = this.getColumns().filter(column => !skippedColumnNames.includes(column.name));
|
|
277
|
+
for (const column of nonSkippedColumns) {
|
|
278
|
+
const columnName = column.name;
|
|
279
|
+
const standaloneFkPropBasedOnColumn = fksOnStandaloneProps.get(columnName);
|
|
280
|
+
if (standaloneFkPropBasedOnColumn && !fksOnColumnProps.get(columnName)) {
|
|
281
|
+
addedStandaloneFkPropsBasedOnColumn.add(columnName);
|
|
282
|
+
const { fkIndex, currentFk } = standaloneFkPropBasedOnColumn;
|
|
283
|
+
const prop = this.getForeignKeyDeclaration(currentFk, namingStrategy, schemaHelper, fkIndex, nullableForeignKeys.has(currentFk), columnName, fksOnColumnProps);
|
|
284
|
+
schema.addProperty(prop.name, prop.type, prop);
|
|
285
|
+
}
|
|
286
|
+
const prop = this.getPropertyDeclaration(column, namingStrategy, schemaHelper, compositeFkIndexes, compositeFkUniques, columnFks, fksOnColumnProps.get(columnName));
|
|
287
|
+
schema.addProperty(prop.name, prop.type, prop);
|
|
288
|
+
}
|
|
289
|
+
for (const [propBaseName, { fkIndex, currentFk }] of fksOnStandaloneProps.entries()) {
|
|
290
|
+
if (addedStandaloneFkPropsBasedOnColumn.has(propBaseName)) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const prop = this.getForeignKeyDeclaration(currentFk, namingStrategy, schemaHelper, fkIndex, nullableForeignKeys.has(currentFk), propBaseName, fksOnColumnProps);
|
|
294
|
+
schema.addProperty(prop.name, prop.type, prop);
|
|
416
295
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
296
|
+
const meta = schema.init().meta;
|
|
297
|
+
const oneToOneCandidateProperties = meta.relations.filter(prop => prop.primary && prop.kind === ReferenceKind.MANY_TO_ONE);
|
|
298
|
+
if (oneToOneCandidateProperties.length === 1 &&
|
|
299
|
+
oneToOneCandidateProperties[0].fieldNames.length ===
|
|
300
|
+
new Set(meta.getPrimaryProps().flatMap(prop => prop.fieldNames)).size) {
|
|
301
|
+
oneToOneCandidateProperties[0].kind = ReferenceKind.ONE_TO_ONE;
|
|
302
|
+
}
|
|
303
|
+
return meta;
|
|
304
|
+
}
|
|
305
|
+
foreignKeysToProps(namingStrategy, scalarPropertiesForRelations) {
|
|
306
|
+
const fks = Object.values(this.getForeignKeys());
|
|
307
|
+
const fksOnColumnProps = new Map();
|
|
308
|
+
const fksOnStandaloneProps = new Map();
|
|
309
|
+
const columnFks = {};
|
|
310
|
+
const fkIndexes = new Map();
|
|
311
|
+
const nullableForeignKeys = new Set();
|
|
312
|
+
const standaloneFksBasedOnColumnNames = new Map();
|
|
313
|
+
for (const currentFk of fks) {
|
|
314
|
+
const fkIndex = this.findFkIndex(currentFk);
|
|
315
|
+
if (currentFk.columnNames.length === 1 &&
|
|
316
|
+
!fks.some(fk => fk !== currentFk && fk.columnNames.length === 1 && currentFk.columnNames[0] === fk.columnNames[0])) {
|
|
317
|
+
// Non-composite FK is the only possible one for a column. Render the column with it.
|
|
318
|
+
const columnName = currentFk.columnNames[0];
|
|
319
|
+
columnFks[columnName] ??= [];
|
|
320
|
+
columnFks[columnName].push(currentFk);
|
|
321
|
+
if (this.getColumn(columnName)?.nullable) {
|
|
322
|
+
nullableForeignKeys.add(currentFk);
|
|
323
|
+
}
|
|
324
|
+
if (scalarPropertiesForRelations === 'always') {
|
|
325
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName);
|
|
326
|
+
standaloneFksBasedOnColumnNames.set(baseName, currentFk);
|
|
327
|
+
fksOnStandaloneProps.set(baseName, { fkIndex, currentFk });
|
|
328
|
+
if (fkIndex) {
|
|
329
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
fksOnColumnProps.set(columnName, currentFk);
|
|
334
|
+
if (fkIndex) {
|
|
335
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName: columnName });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const specificColumnNames = [];
|
|
341
|
+
const nullableColumnsInFk = [];
|
|
342
|
+
for (const columnName of currentFk.columnNames) {
|
|
343
|
+
columnFks[columnName] ??= [];
|
|
344
|
+
columnFks[columnName].push(currentFk);
|
|
345
|
+
if (!fks.some(fk => fk !== currentFk && fk.columnNames.includes(columnName))) {
|
|
346
|
+
specificColumnNames.push(columnName);
|
|
347
|
+
}
|
|
348
|
+
if (this.getColumn(columnName)?.nullable) {
|
|
349
|
+
nullableColumnsInFk.push(columnName);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (nullableColumnsInFk.length > 0) {
|
|
353
|
+
nullableForeignKeys.add(currentFk);
|
|
354
|
+
}
|
|
355
|
+
if (specificColumnNames.length === 1 &&
|
|
356
|
+
(nullableColumnsInFk.length === currentFk.columnNames.length ||
|
|
357
|
+
nullableColumnsInFk.length === 0 ||
|
|
358
|
+
(nullableColumnsInFk.length === 1 && nullableColumnsInFk[0] === specificColumnNames[0]))) {
|
|
359
|
+
// Composite FK has exactly one column which is not used in any other FK.
|
|
360
|
+
// The FK also doesn't have a mix of nullable and non-nullable columns,
|
|
361
|
+
// or its only nullable column is this very one.
|
|
362
|
+
// It is safe to just render this FK attached to the specific column.
|
|
363
|
+
const columnName = specificColumnNames[0];
|
|
364
|
+
if (scalarPropertiesForRelations === 'always') {
|
|
365
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName);
|
|
366
|
+
standaloneFksBasedOnColumnNames.set(baseName, currentFk);
|
|
367
|
+
fksOnStandaloneProps.set(baseName, { fkIndex, currentFk });
|
|
368
|
+
if (fkIndex) {
|
|
369
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
fksOnColumnProps.set(columnName, currentFk);
|
|
374
|
+
if (fkIndex) {
|
|
375
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName: columnName });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (specificColumnNames.length === currentFk.columnNames.length) {
|
|
381
|
+
// All columns involved with this FK are only covered by this one FK.
|
|
382
|
+
if (nullableColumnsInFk.length <= 1) {
|
|
383
|
+
// Also, this FK is either not nullable, or has only one nullable column.
|
|
384
|
+
// It is safe to name the FK after the nullable column, or any non-nullable one (the first one is picked).
|
|
385
|
+
const columnName = nullableColumnsInFk.at(0) ?? currentFk.columnNames[0];
|
|
386
|
+
if (scalarPropertiesForRelations === 'always') {
|
|
387
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName);
|
|
388
|
+
standaloneFksBasedOnColumnNames.set(baseName, currentFk);
|
|
389
|
+
fksOnStandaloneProps.set(baseName, { fkIndex, currentFk });
|
|
390
|
+
if (fkIndex) {
|
|
391
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
fksOnColumnProps.set(columnName, currentFk);
|
|
396
|
+
if (fkIndex) {
|
|
397
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName: columnName });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
// If the first nullable column's name with FK is different from the name without FK,
|
|
403
|
+
// name a standalone prop after the column, but treat the column prop itself as not having FK.
|
|
404
|
+
const columnName = nullableColumnsInFk[0];
|
|
405
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName);
|
|
406
|
+
standaloneFksBasedOnColumnNames.set(baseName, currentFk);
|
|
407
|
+
fksOnStandaloneProps.set(baseName, { fkIndex, currentFk });
|
|
408
|
+
if (fkIndex) {
|
|
409
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
410
|
+
}
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
// FK is not unambiguously mappable to a column. Pick another name for a standalone FK prop.
|
|
414
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks);
|
|
428
415
|
fksOnStandaloneProps.set(baseName, { fkIndex, currentFk });
|
|
429
416
|
if (fkIndex) {
|
|
430
|
-
|
|
417
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
431
418
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
419
|
+
}
|
|
420
|
+
const columnsInFks = Object.keys(columnFks);
|
|
421
|
+
const skippingHandlers = {
|
|
422
|
+
// Never generate scalar props for composite keys,
|
|
423
|
+
// i.e. always skip columns if they are covered by foreign keys.
|
|
424
|
+
never: (column) => columnsInFks.includes(column.name) && !fksOnColumnProps.has(column.name),
|
|
425
|
+
// Always generate scalar props for composite keys,
|
|
426
|
+
// i.e. do not skip columns, even if they are covered by foreign keys.
|
|
427
|
+
always: (column) => false,
|
|
428
|
+
// Smart scalar props generation.
|
|
429
|
+
// Skips columns if they are covered by foreign keys.
|
|
430
|
+
// But also does not skip if the column is not nullable, and yet all involved FKs are nullable,
|
|
431
|
+
// or if one or more FKs involved has multiple nullable columns.
|
|
432
|
+
smart: (column) => {
|
|
433
|
+
return (columnsInFks.includes(column.name) &&
|
|
434
|
+
!fksOnColumnProps.has(column.name) &&
|
|
435
|
+
(column.nullable
|
|
436
|
+
? columnFks[column.name].some(fk => !fk.columnNames.some(fkColumnName => fkColumnName !== column.name && this.getColumn(fkColumnName)?.nullable))
|
|
437
|
+
: columnFks[column.name].some(fk => !nullableForeignKeys.has(fk))));
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
const skippedColumnNames = this.getColumns()
|
|
441
|
+
.filter(skippingHandlers[scalarPropertiesForRelations])
|
|
442
|
+
.map(column => column.name);
|
|
443
|
+
// Check standalone FKs named after columns for potential conflicts among themselves.
|
|
444
|
+
// This typically happens when two standalone FKs named after a column resolve to the same prop name
|
|
445
|
+
// because the respective columns include the referenced table in the name.
|
|
446
|
+
// Depending on naming strategy and actual names, it may also originate from other scenarios.
|
|
447
|
+
// We do our best to de-duplicate them here.
|
|
448
|
+
const safePropNames = new Set();
|
|
449
|
+
const unsafePropNames = new Map();
|
|
450
|
+
for (const [unsafeBaseName, currentFk] of standaloneFksBasedOnColumnNames) {
|
|
451
|
+
const propName = this.getPropertyName(namingStrategy, unsafeBaseName, currentFk);
|
|
452
|
+
if (safePropNames.has(propName)) {
|
|
453
|
+
if (!unsafePropNames.has(propName)) {
|
|
454
|
+
unsafePropNames.set(propName, []);
|
|
455
|
+
}
|
|
456
|
+
unsafePropNames.get(propName).push({ unsafeBaseName, currentFk });
|
|
457
|
+
continue;
|
|
436
458
|
}
|
|
437
|
-
|
|
438
|
-
continue;
|
|
459
|
+
safePropNames.add(propName);
|
|
439
460
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
461
|
+
for (const [unsafePropName, affectedBaseNames] of unsafePropNames) {
|
|
462
|
+
safePropNames.delete(unsafePropName);
|
|
463
|
+
for (const { unsafeBaseName, currentFk } of affectedBaseNames) {
|
|
464
|
+
const newBaseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks);
|
|
465
|
+
fksOnStandaloneProps.delete(unsafeBaseName);
|
|
466
|
+
let fkIndex;
|
|
467
|
+
for (const [indexDef, fkIndexDesc] of fkIndexes) {
|
|
468
|
+
if (fkIndexDesc.fk !== currentFk) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
fkIndexDesc.baseName = newBaseName;
|
|
472
|
+
fkIndex = indexDef;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
fksOnStandaloneProps.set(newBaseName, { fkIndex, currentFk });
|
|
476
|
+
}
|
|
448
477
|
}
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
// FK is not unambiguously mappable to a column. Pick another name for a standalone FK prop.
|
|
452
|
-
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks);
|
|
453
|
-
fksOnStandaloneProps.set(baseName, { fkIndex, currentFk });
|
|
454
|
-
if (fkIndex) {
|
|
455
|
-
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
456
|
-
}
|
|
478
|
+
return { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames };
|
|
457
479
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
(column.nullable
|
|
475
|
-
? columnFks[column.name].some(
|
|
476
|
-
fk =>
|
|
477
|
-
!fk.columnNames.some(
|
|
478
|
-
fkColumnName => fkColumnName !== column.name && this.getColumn(fkColumnName)?.nullable,
|
|
479
|
-
),
|
|
480
|
-
)
|
|
481
|
-
: columnFks[column.name].some(fk => !nullableForeignKeys.has(fk)))
|
|
482
|
-
);
|
|
483
|
-
},
|
|
484
|
-
};
|
|
485
|
-
const skippedColumnNames = this.getColumns()
|
|
486
|
-
.filter(skippingHandlers[scalarPropertiesForRelations])
|
|
487
|
-
.map(column => column.name);
|
|
488
|
-
// Check standalone FKs named after columns for potential conflicts among themselves.
|
|
489
|
-
// This typically happens when two standalone FKs named after a column resolve to the same prop name
|
|
490
|
-
// because the respective columns include the referenced table in the name.
|
|
491
|
-
// Depending on naming strategy and actual names, it may also originate from other scenarios.
|
|
492
|
-
// We do our best to de-duplicate them here.
|
|
493
|
-
const safePropNames = new Set();
|
|
494
|
-
const unsafePropNames = new Map();
|
|
495
|
-
for (const [unsafeBaseName, currentFk] of standaloneFksBasedOnColumnNames) {
|
|
496
|
-
const propName = this.getPropertyName(namingStrategy, unsafeBaseName, currentFk);
|
|
497
|
-
if (safePropNames.has(propName)) {
|
|
498
|
-
if (!unsafePropNames.has(propName)) {
|
|
499
|
-
unsafePropNames.set(propName, []);
|
|
500
|
-
}
|
|
501
|
-
unsafePropNames.get(propName).push({ unsafeBaseName, currentFk });
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
safePropNames.add(propName);
|
|
480
|
+
findFkIndex(currentFk) {
|
|
481
|
+
const fkColumnsLength = currentFk.columnNames.length;
|
|
482
|
+
const possibleIndexes = this.#indexes.filter(index => {
|
|
483
|
+
return (index.columnNames.length === fkColumnsLength &&
|
|
484
|
+
!currentFk.columnNames.some((columnName, i) => index.columnNames[i] !== columnName));
|
|
485
|
+
});
|
|
486
|
+
possibleIndexes.sort((a, b) => {
|
|
487
|
+
if (a.primary !== b.primary) {
|
|
488
|
+
return a.primary ? -1 : 1;
|
|
489
|
+
}
|
|
490
|
+
if (a.unique !== b.unique) {
|
|
491
|
+
return a.unique ? -1 : 1;
|
|
492
|
+
}
|
|
493
|
+
return a.keyName.localeCompare(b.keyName);
|
|
494
|
+
});
|
|
495
|
+
return possibleIndexes.at(0);
|
|
505
496
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
for (const [indexDef, fkIndexDesc] of fkIndexes) {
|
|
513
|
-
if (fkIndexDesc.fk !== currentFk) {
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
fkIndexDesc.baseName = newBaseName;
|
|
517
|
-
fkIndex = indexDef;
|
|
518
|
-
break;
|
|
497
|
+
getIndexProperties(index, columnFks, fksOnColumnProps, fksOnStandaloneProps, namingStrategy) {
|
|
498
|
+
const propBaseNames = new Set();
|
|
499
|
+
const columnNames = index.columnNames;
|
|
500
|
+
const l = columnNames.length;
|
|
501
|
+
if (columnNames.some(col => !col)) {
|
|
502
|
+
return;
|
|
519
503
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
504
|
+
for (let i = 0; i < l; ++i) {
|
|
505
|
+
const columnName = columnNames[i];
|
|
506
|
+
// The column is not involved with FKs.
|
|
507
|
+
if (!(columnName in columnFks)) {
|
|
508
|
+
// If there is no such column, the "name" is actually an expression.
|
|
509
|
+
if (!this.hasColumn(columnName)) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
// It has a prop named after it.
|
|
513
|
+
// Add it and move on.
|
|
514
|
+
propBaseNames.add(columnName);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
// If the prop named after the column has a FK and the FK's columns are a subset of this index,
|
|
518
|
+
// include this prop and move on.
|
|
519
|
+
const columnPropFk = fksOnColumnProps.get(columnName);
|
|
520
|
+
if (columnPropFk && !columnPropFk.columnNames.some(fkColumnName => !columnNames.includes(fkColumnName))) {
|
|
521
|
+
propBaseNames.add(columnName);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
// If there is at least one standalone FK featuring this column,
|
|
525
|
+
// and all of its columns are a subset of this index,
|
|
526
|
+
// include that FK, and consider mapping of this column to a prop a success.
|
|
527
|
+
let propAdded = false;
|
|
528
|
+
for (const [propName, { currentFk: fk }] of fksOnStandaloneProps) {
|
|
529
|
+
if (!columnFks[columnName].includes(fk)) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (!fk.columnNames.some(fkColumnName => !columnNames.includes(fkColumnName))) {
|
|
533
|
+
propBaseNames.add(propName);
|
|
534
|
+
propAdded = true;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (propAdded) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
// If we have reached this point, it means the column is not mappable to a prop name.
|
|
541
|
+
// Break the whole prop creation.
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
return Array.from(propBaseNames).map(baseName => this.getPropertyName(namingStrategy, baseName, fksOnColumnProps.get(baseName)));
|
|
550
545
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
546
|
+
getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName) {
|
|
547
|
+
if (columnName &&
|
|
548
|
+
this.getPropertyName(namingStrategy, columnName, currentFk) !== this.getPropertyName(namingStrategy, columnName)) {
|
|
549
|
+
// The eligible scalar column name is different from the name of the FK prop of the same column.
|
|
550
|
+
// Both can be safely rendered.
|
|
551
|
+
// Use the column name as a base for the FK prop.
|
|
552
|
+
return columnName;
|
|
558
553
|
}
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
// and all of its columns are a subset of this index,
|
|
573
|
-
// include that FK, and consider mapping of this column to a prop a success.
|
|
574
|
-
let propAdded = false;
|
|
575
|
-
for (const [propName, { currentFk: fk }] of fksOnStandaloneProps) {
|
|
576
|
-
if (!columnFks[columnName].includes(fk)) {
|
|
577
|
-
continue;
|
|
554
|
+
// Strip schema prefix from referenced table name (e.g., "public.fr_usuario" -> "fr_usuario")
|
|
555
|
+
const getTableName = (fullName) => {
|
|
556
|
+
const parts = fullName.split('.');
|
|
557
|
+
return parts[parts.length - 1];
|
|
558
|
+
};
|
|
559
|
+
const referencedTableName = getTableName(currentFk.referencedTableName);
|
|
560
|
+
// Check for conflicts using stripped table names (handles cross-schema FKs to same-named tables)
|
|
561
|
+
const hasConflictingFk = fks.some(fk => fk !== currentFk && getTableName(fk.referencedTableName) === referencedTableName);
|
|
562
|
+
if (!hasConflictingFk && !this.getColumn(referencedTableName)) {
|
|
563
|
+
// FK is the only one in this table that references a table with this name.
|
|
564
|
+
// The name of the referenced table is not shared with a column in this table,
|
|
565
|
+
// so it is safe to output prop name based on the referenced entity.
|
|
566
|
+
return referencedTableName;
|
|
578
567
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
568
|
+
// Any ambiguous FK is rendered with a name based on the FK constraint name
|
|
569
|
+
let finalPropBaseName = currentFk.constraintName;
|
|
570
|
+
while (this.getColumn(finalPropBaseName)) {
|
|
571
|
+
// In the unlikely event that the FK constraint name is shared by a column name, generate a name by
|
|
572
|
+
// continuously prefixing with "fk_", until a non-existent column is hit.
|
|
573
|
+
// The worst case scenario is a very long name with several repeated "fk_"
|
|
574
|
+
// that is not really a valid DB identifier but a valid JS variable name.
|
|
575
|
+
finalPropBaseName = `fk_${finalPropBaseName}`;
|
|
582
576
|
}
|
|
583
|
-
|
|
584
|
-
if (propAdded) {
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
// If we have reached this point, it means the column is not mappable to a prop name.
|
|
588
|
-
// Break the whole prop creation.
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
return Array.from(propBaseNames).map(baseName =>
|
|
592
|
-
this.getPropertyName(namingStrategy, baseName, fksOnColumnProps.get(baseName)),
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName) {
|
|
596
|
-
if (
|
|
597
|
-
columnName &&
|
|
598
|
-
this.getPropertyName(namingStrategy, columnName, currentFk) !== this.getPropertyName(namingStrategy, columnName)
|
|
599
|
-
) {
|
|
600
|
-
// The eligible scalar column name is different from the name of the FK prop of the same column.
|
|
601
|
-
// Both can be safely rendered.
|
|
602
|
-
// Use the column name as a base for the FK prop.
|
|
603
|
-
return columnName;
|
|
577
|
+
return finalPropBaseName;
|
|
604
578
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
// FK is the only one in this table that references a table with this name.
|
|
617
|
-
// The name of the referenced table is not shared with a column in this table,
|
|
618
|
-
// so it is safe to output prop name based on the referenced entity.
|
|
619
|
-
return referencedTableName;
|
|
620
|
-
}
|
|
621
|
-
// Any ambiguous FK is rendered with a name based on the FK constraint name
|
|
622
|
-
let finalPropBaseName = currentFk.constraintName;
|
|
623
|
-
while (this.getColumn(finalPropBaseName)) {
|
|
624
|
-
// In the unlikely event that the FK constraint name is shared by a column name, generate a name by
|
|
625
|
-
// continuously prefixing with "fk_", until a non-existent column is hit.
|
|
626
|
-
// The worst case scenario is a very long name with several repeated "fk_"
|
|
627
|
-
// that is not really a valid DB identifier but a valid JS variable name.
|
|
628
|
-
finalPropBaseName = `fk_${finalPropBaseName}`;
|
|
629
|
-
}
|
|
630
|
-
return finalPropBaseName;
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* The shortest name is stripped of the default namespace. All other namespaced elements are returned as full-qualified names.
|
|
634
|
-
*/
|
|
635
|
-
getShortestName(skipDefaultSchema = true) {
|
|
636
|
-
const defaultSchema = this.#platform.getDefaultSchemaName();
|
|
637
|
-
if (
|
|
638
|
-
!this.schema ||
|
|
639
|
-
this.name.startsWith(defaultSchema + '.') ||
|
|
640
|
-
(this.schema === defaultSchema && skipDefaultSchema)
|
|
641
|
-
) {
|
|
642
|
-
return this.name;
|
|
579
|
+
/**
|
|
580
|
+
* The shortest name is stripped of the default namespace. All other namespaced elements are returned as full-qualified names.
|
|
581
|
+
*/
|
|
582
|
+
getShortestName(skipDefaultSchema = true) {
|
|
583
|
+
const defaultSchema = this.#platform.getDefaultSchemaName();
|
|
584
|
+
if (!this.schema ||
|
|
585
|
+
this.name.startsWith(defaultSchema + '.') ||
|
|
586
|
+
(this.schema === defaultSchema && skipDefaultSchema)) {
|
|
587
|
+
return this.name;
|
|
588
|
+
}
|
|
589
|
+
return `${this.schema}.${this.name}`;
|
|
643
590
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
getForeignKeys() {
|
|
647
|
-
return this.#foreignKeys;
|
|
648
|
-
}
|
|
649
|
-
hasColumn(columnName) {
|
|
650
|
-
return columnName in this.#columns;
|
|
651
|
-
}
|
|
652
|
-
getIndex(indexName) {
|
|
653
|
-
return this.#indexes.find(i => i.keyName === indexName);
|
|
654
|
-
}
|
|
655
|
-
hasIndex(indexName) {
|
|
656
|
-
return !!this.getIndex(indexName);
|
|
657
|
-
}
|
|
658
|
-
getCheck(checkName) {
|
|
659
|
-
return this.#checks.find(i => i.name === checkName);
|
|
660
|
-
}
|
|
661
|
-
hasCheck(checkName) {
|
|
662
|
-
return !!this.getCheck(checkName);
|
|
663
|
-
}
|
|
664
|
-
getPrimaryKey() {
|
|
665
|
-
return this.#indexes.find(i => i.primary);
|
|
666
|
-
}
|
|
667
|
-
hasPrimaryKey() {
|
|
668
|
-
return !!this.getPrimaryKey();
|
|
669
|
-
}
|
|
670
|
-
getForeignKeyDeclaration(fk, namingStrategy, schemaHelper, fkIndex, nullable, propNameBase, fksOnColumnProps) {
|
|
671
|
-
const prop = this.getPropertyName(namingStrategy, propNameBase, fk);
|
|
672
|
-
const kind = fkIndex?.unique && !fkIndex.primary ? this.getReferenceKind(fk, fkIndex) : this.getReferenceKind(fk);
|
|
673
|
-
const runtimeType = this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
674
|
-
const fkOptions = {};
|
|
675
|
-
fkOptions.fieldNames = fk.columnNames;
|
|
676
|
-
fkOptions.referencedTableName = fk.referencedTableName;
|
|
677
|
-
fkOptions.referencedColumnNames = fk.referencedColumnNames;
|
|
678
|
-
fkOptions.updateRule = fk.updateRule?.toLowerCase();
|
|
679
|
-
fkOptions.deleteRule = fk.deleteRule?.toLowerCase();
|
|
680
|
-
fkOptions.deferMode = fk.deferMode;
|
|
681
|
-
fkOptions.columnTypes = fk.columnNames.map(c => this.getColumn(c).type);
|
|
682
|
-
const columnOptions = {};
|
|
683
|
-
if (fk.columnNames.length === 1) {
|
|
684
|
-
const column = this.getColumn(fk.columnNames[0]);
|
|
685
|
-
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, column.type, true);
|
|
686
|
-
const defaultTs = this.getPropertyDefaultValue(schemaHelper, column, column.type);
|
|
687
|
-
columnOptions.default = defaultRaw !== defaultTs || defaultRaw === '' ? defaultTs : undefined;
|
|
688
|
-
columnOptions.defaultRaw = column.nullable && defaultRaw === 'null' ? undefined : defaultRaw;
|
|
689
|
-
columnOptions.optional = typeof column.generated !== 'undefined' || defaultRaw !== 'null';
|
|
690
|
-
columnOptions.generated = column.generated;
|
|
691
|
-
columnOptions.nullable = column.nullable;
|
|
692
|
-
columnOptions.primary = column.primary;
|
|
693
|
-
columnOptions.length = column.length;
|
|
694
|
-
columnOptions.precision = column.precision;
|
|
695
|
-
columnOptions.scale = column.scale;
|
|
696
|
-
columnOptions.extra = column.extra;
|
|
697
|
-
columnOptions.comment = column.comment;
|
|
698
|
-
columnOptions.enum = !!column.enumItems?.length;
|
|
699
|
-
columnOptions.items = column.enumItems;
|
|
591
|
+
getForeignKeys() {
|
|
592
|
+
return this.#foreignKeys;
|
|
700
593
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
type: runtimeType,
|
|
704
|
-
runtimeType,
|
|
705
|
-
kind,
|
|
706
|
-
...columnOptions,
|
|
707
|
-
nullable,
|
|
708
|
-
primary:
|
|
709
|
-
fkIndex?.primary || !fk.columnNames.some(columnName => !this.getPrimaryKey()?.columnNames.includes(columnName)),
|
|
710
|
-
index: !fkIndex?.unique ? fkIndex?.keyName : undefined,
|
|
711
|
-
unique: fkIndex?.unique && !fkIndex.primary ? fkIndex.keyName : undefined,
|
|
712
|
-
...fkOptions,
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
getPropertyDeclaration(column, namingStrategy, schemaHelper, compositeFkIndexes, compositeFkUniques, columnFks, fk) {
|
|
716
|
-
const prop = this.getPropertyName(namingStrategy, column.name, fk);
|
|
717
|
-
const persist = !(column.name in columnFks && typeof fk === 'undefined');
|
|
718
|
-
const index =
|
|
719
|
-
compositeFkIndexes[prop] ||
|
|
720
|
-
this.#indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && !idx.unique && !idx.primary);
|
|
721
|
-
const unique =
|
|
722
|
-
compositeFkUniques[prop] ||
|
|
723
|
-
this.#indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && idx.unique && !idx.primary);
|
|
724
|
-
const kind = this.getReferenceKind(fk, unique);
|
|
725
|
-
const runtimeType = this.getPropertyTypeForColumn(namingStrategy, column, fk);
|
|
726
|
-
const type = fk
|
|
727
|
-
? runtimeType
|
|
728
|
-
: (Utils.keys(t).find(k => {
|
|
729
|
-
const typeInCoreMap = this.#platform.getMappedType(k);
|
|
730
|
-
return (
|
|
731
|
-
(typeInCoreMap !== Type.getType(UnknownType) || k === 'unknown') && typeInCoreMap === column.mappedType
|
|
732
|
-
);
|
|
733
|
-
}) ?? runtimeType);
|
|
734
|
-
const ignoreSchemaChanges =
|
|
735
|
-
type === 'unknown' && column.length ? (column.extra ? ['type', 'extra'] : ['type']) : undefined;
|
|
736
|
-
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, runtimeType, true);
|
|
737
|
-
const defaultParsed = this.getPropertyDefaultValue(schemaHelper, column, runtimeType);
|
|
738
|
-
const defaultTs = defaultRaw !== defaultParsed || defaultParsed === '' ? defaultParsed : undefined;
|
|
739
|
-
const fkOptions = {};
|
|
740
|
-
if (fk) {
|
|
741
|
-
fkOptions.fieldNames = fk.columnNames;
|
|
742
|
-
fkOptions.referencedTableName = fk.referencedTableName;
|
|
743
|
-
fkOptions.referencedColumnNames = fk.referencedColumnNames;
|
|
744
|
-
fkOptions.updateRule = fk.updateRule?.toLowerCase();
|
|
745
|
-
fkOptions.deleteRule = fk.deleteRule?.toLowerCase();
|
|
746
|
-
fkOptions.deferMode = fk.deferMode;
|
|
747
|
-
fkOptions.columnTypes = fk.columnNames.map(col => this.getColumn(col).type);
|
|
594
|
+
hasColumn(columnName) {
|
|
595
|
+
return columnName in this.#columns;
|
|
748
596
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
type,
|
|
752
|
-
runtimeType,
|
|
753
|
-
kind,
|
|
754
|
-
ignoreSchemaChanges,
|
|
755
|
-
generated: column.generated,
|
|
756
|
-
optional: defaultRaw !== 'null' || defaultTs != null || typeof column.generated !== 'undefined',
|
|
757
|
-
columnType: column.type,
|
|
758
|
-
default: defaultTs,
|
|
759
|
-
defaultRaw: column.nullable && defaultRaw === 'null' ? undefined : defaultRaw,
|
|
760
|
-
nullable: column.nullable,
|
|
761
|
-
primary: column.primary && persist,
|
|
762
|
-
autoincrement: column.autoincrement,
|
|
763
|
-
fieldName: column.name,
|
|
764
|
-
unsigned: column.unsigned,
|
|
765
|
-
length: column.length,
|
|
766
|
-
precision: column.precision,
|
|
767
|
-
scale: column.scale,
|
|
768
|
-
extra: column.extra,
|
|
769
|
-
comment: column.comment,
|
|
770
|
-
index: index ? index.keyName : undefined,
|
|
771
|
-
unique: unique ? unique.keyName : undefined,
|
|
772
|
-
enum: !!column.enumItems?.length,
|
|
773
|
-
items: column.enumItems,
|
|
774
|
-
persist,
|
|
775
|
-
...fkOptions,
|
|
776
|
-
};
|
|
777
|
-
const nativeEnumName = Object.keys(this.nativeEnums).find(name => name === column.type);
|
|
778
|
-
if (nativeEnumName) {
|
|
779
|
-
ret.nativeEnumName = nativeEnumName;
|
|
597
|
+
getIndex(indexName) {
|
|
598
|
+
return this.#indexes.find(i => i.keyName === indexName);
|
|
780
599
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
getReferenceKind(fk, unique) {
|
|
784
|
-
if (fk && unique) {
|
|
785
|
-
return ReferenceKind.ONE_TO_ONE;
|
|
600
|
+
hasIndex(indexName) {
|
|
601
|
+
return !!this.getIndex(indexName);
|
|
786
602
|
}
|
|
787
|
-
|
|
788
|
-
|
|
603
|
+
getCheck(checkName) {
|
|
604
|
+
return this.#checks.find(i => i.name === checkName);
|
|
789
605
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
getPropertyName(namingStrategy, baseName, fk) {
|
|
793
|
-
let field = baseName;
|
|
794
|
-
if (fk) {
|
|
795
|
-
const idx = fk.columnNames.indexOf(baseName);
|
|
796
|
-
let replacedFieldName = field.replace(new RegExp(`_${fk.referencedColumnNames[idx]}$`), '');
|
|
797
|
-
if (replacedFieldName === field) {
|
|
798
|
-
replacedFieldName = field.replace(new RegExp(`_${namingStrategy.referenceColumnName()}$`), '');
|
|
799
|
-
}
|
|
800
|
-
field = replacedFieldName;
|
|
606
|
+
hasCheck(checkName) {
|
|
607
|
+
return !!this.getCheck(checkName);
|
|
801
608
|
}
|
|
802
|
-
|
|
803
|
-
|
|
609
|
+
getPrimaryKey() {
|
|
610
|
+
return this.#indexes.find(i => i.primary);
|
|
804
611
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
getPropertyTypeForForeignKey(namingStrategy, fk) {
|
|
808
|
-
const parts = fk.referencedTableName.split('.', 2);
|
|
809
|
-
return namingStrategy.getEntityName(...parts.reverse());
|
|
810
|
-
}
|
|
811
|
-
getPropertyTypeForColumn(namingStrategy, column, fk) {
|
|
812
|
-
if (fk) {
|
|
813
|
-
return this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
612
|
+
hasPrimaryKey() {
|
|
613
|
+
return !!this.getPrimaryKey();
|
|
814
614
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
615
|
+
getForeignKeyDeclaration(fk, namingStrategy, schemaHelper, fkIndex, nullable, propNameBase, fksOnColumnProps) {
|
|
616
|
+
const prop = this.getPropertyName(namingStrategy, propNameBase, fk);
|
|
617
|
+
const kind = fkIndex?.unique && !fkIndex.primary ? this.getReferenceKind(fk, fkIndex) : this.getReferenceKind(fk);
|
|
618
|
+
const runtimeType = this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
619
|
+
const fkOptions = {};
|
|
620
|
+
fkOptions.fieldNames = fk.columnNames;
|
|
621
|
+
fkOptions.referencedTableName = fk.referencedTableName;
|
|
622
|
+
fkOptions.referencedColumnNames = fk.referencedColumnNames;
|
|
623
|
+
fkOptions.updateRule = fk.updateRule?.toLowerCase();
|
|
624
|
+
fkOptions.deleteRule = fk.deleteRule?.toLowerCase();
|
|
625
|
+
fkOptions.deferMode = fk.deferMode;
|
|
626
|
+
fkOptions.columnTypes = fk.columnNames.map(c => this.getColumn(c).type);
|
|
627
|
+
const columnOptions = {};
|
|
628
|
+
if (fk.columnNames.length === 1) {
|
|
629
|
+
const column = this.getColumn(fk.columnNames[0]);
|
|
630
|
+
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, column.type, true);
|
|
631
|
+
const defaultTs = this.getPropertyDefaultValue(schemaHelper, column, column.type);
|
|
632
|
+
columnOptions.default = defaultRaw !== defaultTs || defaultRaw === '' ? defaultTs : undefined;
|
|
633
|
+
columnOptions.defaultRaw = column.nullable && defaultRaw === 'null' ? undefined : defaultRaw;
|
|
634
|
+
columnOptions.optional = typeof column.generated !== 'undefined' || defaultRaw !== 'null';
|
|
635
|
+
columnOptions.generated = column.generated;
|
|
636
|
+
columnOptions.nullable = column.nullable;
|
|
637
|
+
columnOptions.primary = column.primary;
|
|
638
|
+
columnOptions.length = column.length;
|
|
639
|
+
columnOptions.precision = column.precision;
|
|
640
|
+
columnOptions.scale = column.scale;
|
|
641
|
+
columnOptions.extra = column.extra;
|
|
642
|
+
columnOptions.comment = column.comment;
|
|
643
|
+
columnOptions.enum = !!column.enumItems?.length;
|
|
644
|
+
columnOptions.items = column.enumItems;
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
name: prop,
|
|
648
|
+
type: runtimeType,
|
|
649
|
+
runtimeType,
|
|
650
|
+
kind,
|
|
651
|
+
...columnOptions,
|
|
652
|
+
nullable,
|
|
653
|
+
primary: fkIndex?.primary || !fk.columnNames.some(columnName => !this.getPrimaryKey()?.columnNames.includes(columnName)),
|
|
654
|
+
index: !fkIndex?.unique ? fkIndex?.keyName : undefined,
|
|
655
|
+
unique: fkIndex?.unique && !fkIndex.primary ? fkIndex.keyName : undefined,
|
|
656
|
+
...fkOptions,
|
|
657
|
+
};
|
|
826
658
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
659
|
+
getPropertyDeclaration(column, namingStrategy, schemaHelper, compositeFkIndexes, compositeFkUniques, columnFks, fk) {
|
|
660
|
+
const prop = this.getPropertyName(namingStrategy, column.name, fk);
|
|
661
|
+
const persist = !(column.name in columnFks && typeof fk === 'undefined');
|
|
662
|
+
const index = compositeFkIndexes[prop] ||
|
|
663
|
+
this.#indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && !idx.unique && !idx.primary);
|
|
664
|
+
const unique = compositeFkUniques[prop] ||
|
|
665
|
+
this.#indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && idx.unique && !idx.primary);
|
|
666
|
+
const kind = this.getReferenceKind(fk, unique);
|
|
667
|
+
const runtimeType = this.getPropertyTypeForColumn(namingStrategy, column, fk);
|
|
668
|
+
const type = fk
|
|
669
|
+
? runtimeType
|
|
670
|
+
: (Utils.keys(t).find(k => {
|
|
671
|
+
const typeInCoreMap = this.#platform.getMappedType(k);
|
|
672
|
+
return ((typeInCoreMap !== Type.getType(UnknownType) || k === 'unknown') && typeInCoreMap === column.mappedType);
|
|
673
|
+
}) ?? runtimeType);
|
|
674
|
+
const ignoreSchemaChanges = type === 'unknown' && column.length ? (column.extra ? ['type', 'extra'] : ['type']) : undefined;
|
|
675
|
+
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, runtimeType, true);
|
|
676
|
+
const defaultParsed = this.getPropertyDefaultValue(schemaHelper, column, runtimeType);
|
|
677
|
+
const defaultTs = defaultRaw !== defaultParsed || defaultParsed === '' ? defaultParsed : undefined;
|
|
678
|
+
const fkOptions = {};
|
|
679
|
+
if (fk) {
|
|
680
|
+
fkOptions.fieldNames = fk.columnNames;
|
|
681
|
+
fkOptions.referencedTableName = fk.referencedTableName;
|
|
682
|
+
fkOptions.referencedColumnNames = fk.referencedColumnNames;
|
|
683
|
+
fkOptions.updateRule = fk.updateRule?.toLowerCase();
|
|
684
|
+
fkOptions.deleteRule = fk.deleteRule?.toLowerCase();
|
|
685
|
+
fkOptions.deferMode = fk.deferMode;
|
|
686
|
+
fkOptions.columnTypes = fk.columnNames.map(col => this.getColumn(col).type);
|
|
687
|
+
}
|
|
688
|
+
const ret = {
|
|
689
|
+
name: prop,
|
|
690
|
+
type,
|
|
691
|
+
runtimeType,
|
|
692
|
+
kind,
|
|
693
|
+
ignoreSchemaChanges,
|
|
694
|
+
generated: column.generated,
|
|
695
|
+
optional: defaultRaw !== 'null' || defaultTs != null || typeof column.generated !== 'undefined',
|
|
696
|
+
columnType: column.type,
|
|
697
|
+
default: defaultTs,
|
|
698
|
+
defaultRaw: column.nullable && defaultRaw === 'null' ? undefined : defaultRaw,
|
|
699
|
+
nullable: column.nullable,
|
|
700
|
+
primary: column.primary && persist,
|
|
701
|
+
autoincrement: column.autoincrement,
|
|
702
|
+
fieldName: column.name,
|
|
703
|
+
unsigned: column.unsigned,
|
|
704
|
+
length: column.length,
|
|
705
|
+
precision: column.precision,
|
|
706
|
+
scale: column.scale,
|
|
707
|
+
extra: column.extra,
|
|
708
|
+
comment: column.comment,
|
|
709
|
+
index: index ? index.keyName : undefined,
|
|
710
|
+
unique: unique ? unique.keyName : undefined,
|
|
711
|
+
enum: !!column.enumItems?.length,
|
|
712
|
+
items: column.enumItems,
|
|
713
|
+
persist,
|
|
714
|
+
...fkOptions,
|
|
715
|
+
};
|
|
716
|
+
const nativeEnumName = Object.keys(this.nativeEnums).find(name => name === column.type);
|
|
717
|
+
if (nativeEnumName) {
|
|
718
|
+
ret.nativeEnumName = nativeEnumName;
|
|
719
|
+
}
|
|
720
|
+
return ret;
|
|
834
721
|
}
|
|
835
|
-
|
|
836
|
-
|
|
722
|
+
getReferenceKind(fk, unique) {
|
|
723
|
+
if (fk && unique) {
|
|
724
|
+
return ReferenceKind.ONE_TO_ONE;
|
|
725
|
+
}
|
|
726
|
+
if (fk) {
|
|
727
|
+
return ReferenceKind.MANY_TO_ONE;
|
|
728
|
+
}
|
|
729
|
+
return ReferenceKind.SCALAR;
|
|
837
730
|
}
|
|
838
|
-
|
|
839
|
-
|
|
731
|
+
getPropertyName(namingStrategy, baseName, fk) {
|
|
732
|
+
let field = baseName;
|
|
733
|
+
if (fk) {
|
|
734
|
+
const idx = fk.columnNames.indexOf(baseName);
|
|
735
|
+
let replacedFieldName = field.replace(new RegExp(`_${fk.referencedColumnNames[idx]}$`), '');
|
|
736
|
+
if (replacedFieldName === field) {
|
|
737
|
+
replacedFieldName = field.replace(new RegExp(`_${namingStrategy.referenceColumnName()}$`), '');
|
|
738
|
+
}
|
|
739
|
+
field = replacedFieldName;
|
|
740
|
+
}
|
|
741
|
+
if (field.startsWith('_')) {
|
|
742
|
+
return field;
|
|
743
|
+
}
|
|
744
|
+
return namingStrategy.columnNameToProperty(field);
|
|
840
745
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
return match[1];
|
|
746
|
+
getPropertyTypeForForeignKey(namingStrategy, fk) {
|
|
747
|
+
const parts = fk.referencedTableName.split('.', 2);
|
|
748
|
+
return namingStrategy.getEntityName(...parts.reverse());
|
|
845
749
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
750
|
+
getPropertyTypeForColumn(namingStrategy, column, fk) {
|
|
751
|
+
if (fk) {
|
|
752
|
+
return this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
753
|
+
}
|
|
754
|
+
const enumMode = this.#platform.getConfig().get('entityGenerator').enumMode;
|
|
755
|
+
// If this column is using an enum.
|
|
756
|
+
if (column.enumItems?.length) {
|
|
757
|
+
const name = column.nativeEnumName ?? column.name;
|
|
758
|
+
const tableName = column.nativeEnumName ? undefined : this.name;
|
|
759
|
+
if (enumMode === 'ts-enum') {
|
|
760
|
+
// We will create a new enum name for this type and set it as the property type as well.
|
|
761
|
+
return namingStrategy.getEnumClassName(name, tableName, this.schema);
|
|
762
|
+
}
|
|
763
|
+
// With other enum strategies, we need to use the type name.
|
|
764
|
+
return namingStrategy.getEnumTypeName(name, tableName, this.schema);
|
|
765
|
+
}
|
|
766
|
+
return column.mappedType?.runtimeType ?? 'unknown';
|
|
860
767
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
768
|
+
getPropertyDefaultValue(schemaHelper, column, propType, raw = false) {
|
|
769
|
+
const defaultValue = column.default ?? 'null';
|
|
770
|
+
const val = schemaHelper.normalizeDefaultValue(defaultValue, column.length);
|
|
771
|
+
if (val === 'null') {
|
|
772
|
+
return raw ? 'null' : column.nullable ? null : undefined;
|
|
773
|
+
}
|
|
774
|
+
if (propType === 'boolean' && !raw) {
|
|
775
|
+
return !['0', 'false', 'f', 'n', 'no', 'off'].includes('' + column.default);
|
|
776
|
+
}
|
|
777
|
+
if (propType === 'number' && !raw) {
|
|
778
|
+
return +defaultValue;
|
|
779
|
+
}
|
|
780
|
+
// unquote string defaults if `raw = false`
|
|
781
|
+
const match = /^'(.*)'$/.exec('' + val);
|
|
782
|
+
if (!raw && match) {
|
|
783
|
+
return match[1];
|
|
784
|
+
}
|
|
785
|
+
return '' + val;
|
|
871
786
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
return meta.properties[prop].fieldNames;
|
|
882
|
-
}
|
|
883
|
-
const rootProp = meta.properties[root];
|
|
884
|
-
// inline embedded property index, we need to find the field name of the child property
|
|
885
|
-
if (rootProp?.embeddable && !rootProp.object && parts.length > 1) {
|
|
886
|
-
const expand = (p, i) => {
|
|
887
|
-
if (parts.length === i) {
|
|
888
|
-
return p.fieldNames[0];
|
|
889
|
-
}
|
|
890
|
-
return expand(p.embeddedProps[parts[i]], i + 1);
|
|
787
|
+
processIndexExpression(indexName, expression, meta) {
|
|
788
|
+
if (expression instanceof Function) {
|
|
789
|
+
const qualifiedName = this.schema ? `${this.schema}.${this.name}` : this.name;
|
|
790
|
+
const table = {
|
|
791
|
+
name: this.name,
|
|
792
|
+
schema: this.schema,
|
|
793
|
+
qualifiedName,
|
|
794
|
+
toString: () => qualifiedName,
|
|
891
795
|
};
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
}
|
|
898
|
-
/* v8 ignore next */
|
|
899
|
-
return [prop];
|
|
900
|
-
}),
|
|
901
|
-
),
|
|
902
|
-
);
|
|
903
|
-
if (properties.length === 0 && !index.expression) {
|
|
904
|
-
return;
|
|
796
|
+
const columns = meta.createSchemaColumnMappingObject();
|
|
797
|
+
const exp = expression(columns, table, indexName);
|
|
798
|
+
return isRaw(exp) ? this.#platform.formatQuery(exp.sql, exp.params) : exp;
|
|
799
|
+
}
|
|
800
|
+
return expression;
|
|
905
801
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
802
|
+
addIndex(meta, index, type) {
|
|
803
|
+
// If columns are specified but properties are not, derive properties from column names
|
|
804
|
+
if (index.columns?.length &&
|
|
805
|
+
!index.expression &&
|
|
806
|
+
(!index.properties || Utils.asArray(index.properties).length === 0)) {
|
|
807
|
+
index = { ...index, properties: index.columns.map(c => c.name) };
|
|
808
|
+
}
|
|
809
|
+
const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
|
|
810
|
+
const parts = prop.split('.');
|
|
811
|
+
const root = parts[0];
|
|
812
|
+
if (meta.properties[prop]) {
|
|
813
|
+
if (meta.properties[prop].embeddedPath) {
|
|
814
|
+
return [meta.properties[prop].embeddedPath.join('.')];
|
|
815
|
+
}
|
|
913
816
|
return meta.properties[prop].fieldNames;
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
if (!properties.includes(col.name)) {
|
|
936
|
-
throw new Error(
|
|
937
|
-
`Index '${name}' on entity '${meta.className}': column option references field '${col.name}' which is not in the index properties`,
|
|
938
|
-
);
|
|
817
|
+
}
|
|
818
|
+
const rootProp = meta.properties[root];
|
|
819
|
+
// inline embedded property index, we need to find the field name of the child property
|
|
820
|
+
if (rootProp?.embeddable && !rootProp.object && parts.length > 1) {
|
|
821
|
+
const expand = (p, i) => {
|
|
822
|
+
if (parts.length === i) {
|
|
823
|
+
return p.fieldNames[0];
|
|
824
|
+
}
|
|
825
|
+
return expand(p.embeddedProps[parts[i]], i + 1);
|
|
826
|
+
};
|
|
827
|
+
return [expand(rootProp, 1)];
|
|
828
|
+
}
|
|
829
|
+
// json index, we need to rename the column only
|
|
830
|
+
if (rootProp) {
|
|
831
|
+
return [prop.replace(root, rootProp.fieldNames[0])];
|
|
832
|
+
}
|
|
833
|
+
/* v8 ignore next */
|
|
834
|
+
return [prop];
|
|
835
|
+
})));
|
|
836
|
+
if (properties.length === 0 && !index.expression) {
|
|
837
|
+
return;
|
|
939
838
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
this.#checks.push(check);
|
|
970
|
-
}
|
|
971
|
-
toJSON() {
|
|
972
|
-
const columns = this.#columns;
|
|
973
|
-
// locale-independent comparison so the snapshot is stable across machines
|
|
974
|
-
const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
975
|
-
const sortedColumnKeys = Utils.keys(columns).sort(byString);
|
|
976
|
-
// mirror `DatabaseTable.init()`: derive `primary`/`unique` from index membership
|
|
977
|
-
// so metadata (which keeps `primary: false` on composite PK columns) and introspection agree
|
|
978
|
-
const primaryColumns = new Set();
|
|
979
|
-
const uniqueColumns = new Set();
|
|
980
|
-
for (const idx of this.#indexes) {
|
|
981
|
-
if (idx.primary) {
|
|
982
|
-
idx.columnNames.forEach(c => primaryColumns.add(c));
|
|
983
|
-
}
|
|
984
|
-
if (idx.unique && !idx.primary && idx.columnNames.length > 0) {
|
|
985
|
-
uniqueColumns.add(idx.columnNames[0]);
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
// integer/float widths live in the type name (`int2`/`int4`/`float8`) — drop redundant precision/scale
|
|
989
|
-
const isFixedPrecisionFamily = mappedType =>
|
|
990
|
-
mappedType instanceof t.integer ||
|
|
991
|
-
mappedType instanceof t.smallint ||
|
|
992
|
-
mappedType instanceof t.tinyint ||
|
|
993
|
-
mappedType instanceof t.mediumint ||
|
|
994
|
-
mappedType instanceof t.bigint ||
|
|
995
|
-
mappedType instanceof t.float ||
|
|
996
|
-
mappedType instanceof t.double;
|
|
997
|
-
const supportsUnsigned = this.#platform.supportsUnsigned();
|
|
998
|
-
const columnsMapped = sortedColumnKeys.reduce((o, col) => {
|
|
999
|
-
const c = columns[col];
|
|
1000
|
-
// omit `autoincrement` from options so `serial` (metadata) and `int4` (introspection) collapse the same
|
|
1001
|
-
const rawType = c.type?.toLowerCase();
|
|
1002
|
-
const normOptions = { length: c.length, precision: c.precision, scale: c.scale };
|
|
1003
|
-
const type = this.#platform.normalizeColumnType(c.type ?? '', normOptions)?.toLowerCase() || rawType;
|
|
1004
|
-
const fixedPrecision = isFixedPrecisionFamily(c.mappedType);
|
|
1005
|
-
const normalized = {
|
|
1006
|
-
name: c.name,
|
|
1007
|
-
type,
|
|
1008
|
-
unsigned: supportsUnsigned && !!c.unsigned,
|
|
1009
|
-
autoincrement: !!c.autoincrement,
|
|
1010
|
-
primary: primaryColumns.has(c.name) || !!c.primary,
|
|
1011
|
-
nullable: !!c.nullable,
|
|
1012
|
-
unique: uniqueColumns.has(c.name) || !!c.unique,
|
|
1013
|
-
length: c.length || null,
|
|
1014
|
-
precision: fixedPrecision ? null : (c.precision ?? null),
|
|
1015
|
-
scale: fixedPrecision ? null : (c.scale ?? null),
|
|
1016
|
-
default: c.default ?? null,
|
|
1017
|
-
comment: c.comment ?? null,
|
|
1018
|
-
enumItems: c.enumItems ?? [],
|
|
1019
|
-
mappedType: Utils.keys(t).find(k => t[k] === c.mappedType.constructor),
|
|
1020
|
-
};
|
|
1021
|
-
for (const field of ['generated', 'nativeEnumName', 'extra', 'ignoreSchemaChanges', 'defaultConstraint']) {
|
|
1022
|
-
if (c[field]) {
|
|
1023
|
-
normalized[field] = c[field];
|
|
839
|
+
const name = this.getIndexName(index.name, properties, type);
|
|
840
|
+
// Process include columns (map property names to field names)
|
|
841
|
+
const includeColumns = index.include
|
|
842
|
+
? Utils.unique(Utils.flatten(Utils.asArray(index.include).map(prop => {
|
|
843
|
+
if (meta.properties[prop]) {
|
|
844
|
+
return meta.properties[prop].fieldNames;
|
|
845
|
+
}
|
|
846
|
+
/* v8 ignore next */
|
|
847
|
+
return [prop];
|
|
848
|
+
})))
|
|
849
|
+
: undefined;
|
|
850
|
+
// Process columns with advanced options (map property names to field names)
|
|
851
|
+
const columns = index.columns?.map(col => {
|
|
852
|
+
const fieldName = meta.properties[col.name]?.fieldNames[0] ?? col.name;
|
|
853
|
+
return {
|
|
854
|
+
name: fieldName,
|
|
855
|
+
sort: col.sort?.toUpperCase(),
|
|
856
|
+
nulls: col.nulls?.toUpperCase(),
|
|
857
|
+
length: col.length,
|
|
858
|
+
collation: col.collation,
|
|
859
|
+
};
|
|
860
|
+
});
|
|
861
|
+
// Validate that column options reference fields in the index properties
|
|
862
|
+
if (columns?.length && properties.length > 0) {
|
|
863
|
+
for (const col of columns) {
|
|
864
|
+
if (!properties.includes(col.name)) {
|
|
865
|
+
throw new Error(`Index '${name}' on entity '${meta.className}': column option references field '${col.name}' which is not in the index properties`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
1024
868
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
}, {});
|
|
1029
|
-
const normalizeIndex = idx => {
|
|
1030
|
-
const out = {
|
|
1031
|
-
columnNames: idx.columnNames,
|
|
1032
|
-
composite: !!idx.composite,
|
|
1033
|
-
// PK indexes are always backed by a constraint — force it so postgres introspection matches
|
|
1034
|
-
constraint: !!idx.constraint || !!idx.primary,
|
|
1035
|
-
keyName: idx.keyName,
|
|
1036
|
-
primary: !!idx.primary,
|
|
1037
|
-
unique: !!idx.unique,
|
|
1038
|
-
};
|
|
1039
|
-
const optional = [
|
|
1040
|
-
'expression',
|
|
1041
|
-
'type',
|
|
1042
|
-
'deferMode',
|
|
1043
|
-
'columns',
|
|
1044
|
-
'include',
|
|
1045
|
-
'fillFactor',
|
|
1046
|
-
'invisible',
|
|
1047
|
-
'disabled',
|
|
1048
|
-
'clustered',
|
|
1049
|
-
];
|
|
1050
|
-
for (const field of optional) {
|
|
1051
|
-
if (idx[field] != null && idx[field] !== false) {
|
|
1052
|
-
out[field] = idx[field];
|
|
869
|
+
// Validate fillFactor range
|
|
870
|
+
if (index.fillFactor != null && (index.fillFactor < 0 || index.fillFactor > 100)) {
|
|
871
|
+
throw new Error(`fillFactor must be between 0 and 100, got ${index.fillFactor} for index '${name}' on entity '${meta.className}'`);
|
|
1053
872
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
873
|
+
this.#indexes.push({
|
|
874
|
+
keyName: name,
|
|
875
|
+
columnNames: properties,
|
|
876
|
+
composite: properties.length > 1,
|
|
877
|
+
// JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
|
|
878
|
+
constraint: type !== 'index' && !properties.some((d) => d.includes('.')),
|
|
879
|
+
primary: type === 'primary',
|
|
880
|
+
unique: type !== 'index',
|
|
881
|
+
type: index.type,
|
|
882
|
+
expression: this.processIndexExpression(name, index.expression, meta),
|
|
883
|
+
options: index.options,
|
|
884
|
+
deferMode: index.deferMode,
|
|
885
|
+
columns,
|
|
886
|
+
include: includeColumns,
|
|
887
|
+
fillFactor: index.fillFactor,
|
|
888
|
+
invisible: index.invisible,
|
|
889
|
+
disabled: index.disabled,
|
|
890
|
+
clustered: index.clustered,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
addCheck(check) {
|
|
894
|
+
this.#checks.push(check);
|
|
895
|
+
}
|
|
896
|
+
toJSON() {
|
|
897
|
+
const columns = this.#columns;
|
|
898
|
+
// locale-independent comparison so the snapshot is stable across machines
|
|
899
|
+
const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
900
|
+
const sortedColumnKeys = Utils.keys(columns).sort(byString);
|
|
901
|
+
// mirror `DatabaseTable.init()`: derive `primary`/`unique` from index membership
|
|
902
|
+
// so metadata (which keeps `primary: false` on composite PK columns) and introspection agree
|
|
903
|
+
const primaryColumns = new Set();
|
|
904
|
+
const uniqueColumns = new Set();
|
|
905
|
+
for (const idx of this.#indexes) {
|
|
906
|
+
if (idx.primary) {
|
|
907
|
+
idx.columnNames.forEach(c => primaryColumns.add(c));
|
|
908
|
+
}
|
|
909
|
+
if (idx.unique && !idx.primary && idx.columnNames.length > 0) {
|
|
910
|
+
uniqueColumns.add(idx.columnNames[0]);
|
|
911
|
+
}
|
|
1079
912
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
913
|
+
// integer/float widths live in the type name (`int2`/`int4`/`float8`) — drop redundant precision/scale
|
|
914
|
+
const isFixedPrecisionFamily = (mappedType) => mappedType instanceof t.integer ||
|
|
915
|
+
mappedType instanceof t.smallint ||
|
|
916
|
+
mappedType instanceof t.tinyint ||
|
|
917
|
+
mappedType instanceof t.mediumint ||
|
|
918
|
+
mappedType instanceof t.bigint ||
|
|
919
|
+
mappedType instanceof t.float ||
|
|
920
|
+
mappedType instanceof t.double;
|
|
921
|
+
const supportsUnsigned = this.#platform.supportsUnsigned();
|
|
922
|
+
const columnsMapped = sortedColumnKeys.reduce((o, col) => {
|
|
923
|
+
const c = columns[col];
|
|
924
|
+
// omit `autoincrement` from options so `serial` (metadata) and `int4` (introspection) collapse the same
|
|
925
|
+
const rawType = c.type?.toLowerCase();
|
|
926
|
+
const normOptions = { length: c.length, precision: c.precision, scale: c.scale };
|
|
927
|
+
const type = this.#platform.normalizeColumnType(c.type ?? '', normOptions)?.toLowerCase() || rawType;
|
|
928
|
+
const fixedPrecision = isFixedPrecisionFamily(c.mappedType);
|
|
929
|
+
const normalized = {
|
|
930
|
+
name: c.name,
|
|
931
|
+
type,
|
|
932
|
+
unsigned: supportsUnsigned && !!c.unsigned,
|
|
933
|
+
autoincrement: !!c.autoincrement,
|
|
934
|
+
primary: primaryColumns.has(c.name) || !!c.primary,
|
|
935
|
+
nullable: !!c.nullable,
|
|
936
|
+
unique: uniqueColumns.has(c.name) || !!c.unique,
|
|
937
|
+
length: c.length || null,
|
|
938
|
+
precision: fixedPrecision ? null : (c.precision ?? null),
|
|
939
|
+
scale: fixedPrecision ? null : (c.scale ?? null),
|
|
940
|
+
default: c.default ?? null,
|
|
941
|
+
comment: c.comment ?? null,
|
|
942
|
+
enumItems: c.enumItems ?? [],
|
|
943
|
+
mappedType: Utils.keys(t).find(k => t[k] === c.mappedType.constructor),
|
|
944
|
+
};
|
|
945
|
+
for (const field of [
|
|
946
|
+
'generated',
|
|
947
|
+
'nativeEnumName',
|
|
948
|
+
'extra',
|
|
949
|
+
'ignoreSchemaChanges',
|
|
950
|
+
'defaultConstraint',
|
|
951
|
+
]) {
|
|
952
|
+
if (c[field]) {
|
|
953
|
+
normalized[field] = c[field];
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
o[col] = normalized;
|
|
957
|
+
return o;
|
|
958
|
+
}, {});
|
|
959
|
+
const normalizeIndex = (idx) => {
|
|
960
|
+
const out = {
|
|
961
|
+
columnNames: idx.columnNames,
|
|
962
|
+
composite: !!idx.composite,
|
|
963
|
+
// PK indexes are always backed by a constraint — force it so postgres introspection matches
|
|
964
|
+
constraint: !!idx.constraint || !!idx.primary,
|
|
965
|
+
keyName: idx.keyName,
|
|
966
|
+
primary: !!idx.primary,
|
|
967
|
+
unique: !!idx.unique,
|
|
968
|
+
};
|
|
969
|
+
const optional = [
|
|
970
|
+
'expression',
|
|
971
|
+
'type',
|
|
972
|
+
'deferMode',
|
|
973
|
+
'columns',
|
|
974
|
+
'include',
|
|
975
|
+
'fillFactor',
|
|
976
|
+
'invisible',
|
|
977
|
+
'disabled',
|
|
978
|
+
'clustered',
|
|
979
|
+
];
|
|
980
|
+
for (const field of optional) {
|
|
981
|
+
if (idx[field] != null && idx[field] !== false) {
|
|
982
|
+
out[field] = idx[field];
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return out;
|
|
986
|
+
};
|
|
987
|
+
const normalizeFk = (fk) => {
|
|
988
|
+
const isNoAction = (rule) => !rule || rule.toLowerCase() === 'no action';
|
|
989
|
+
// JSON.stringify drops undefined properties — let them through instead of guarding
|
|
990
|
+
return {
|
|
991
|
+
columnNames: fk.columnNames,
|
|
992
|
+
constraintName: fk.constraintName,
|
|
993
|
+
localTableName: fk.localTableName,
|
|
994
|
+
referencedColumnNames: fk.referencedColumnNames,
|
|
995
|
+
referencedTableName: fk.referencedTableName,
|
|
996
|
+
updateRule: isNoAction(fk.updateRule) ? undefined : fk.updateRule,
|
|
997
|
+
deleteRule: isNoAction(fk.deleteRule) ? undefined : fk.deleteRule,
|
|
998
|
+
deferMode: fk.deferMode,
|
|
999
|
+
};
|
|
1000
|
+
};
|
|
1001
|
+
const normalizeCheck = (check) => {
|
|
1002
|
+
const out = { name: check.name };
|
|
1003
|
+
if (typeof check.expression === 'string') {
|
|
1004
|
+
out.expression = check.expression;
|
|
1005
|
+
}
|
|
1006
|
+
for (const field of ['definition', 'columnName']) {
|
|
1007
|
+
if (check[field]) {
|
|
1008
|
+
out[field] = check[field];
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return out;
|
|
1012
|
+
};
|
|
1013
|
+
const sortedIndexes = [...this.#indexes].sort((a, b) => byString(a.keyName, b.keyName)).map(normalizeIndex);
|
|
1014
|
+
const sortedChecks = [...this.#checks].sort((a, b) => byString(a.name, b.name)).map(normalizeCheck);
|
|
1015
|
+
const sortedForeignKeys = Object.fromEntries(Object.entries(this.#foreignKeys)
|
|
1016
|
+
.sort(([a], [b]) => byString(a, b))
|
|
1017
|
+
.map(([k, v]) => [k, normalizeFk(v)]));
|
|
1018
|
+
return {
|
|
1019
|
+
name: this.name,
|
|
1020
|
+
schema: this.schema,
|
|
1021
|
+
columns: columnsMapped,
|
|
1022
|
+
indexes: sortedIndexes,
|
|
1023
|
+
checks: sortedChecks,
|
|
1024
|
+
foreignKeys: sortedForeignKeys,
|
|
1025
|
+
// emit `comment` even when unset so introspection (which always reads it) matches metadata
|
|
1026
|
+
comment: this.comment ?? null,
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1101
1029
|
}
|