@mikro-orm/knex 6.0.0-dev.18 → 6.0.0-dev.180
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 +8 -4
- package/AbstractSqlConnection.js +30 -18
- package/AbstractSqlDriver.d.ts +21 -23
- package/AbstractSqlDriver.js +389 -224
- package/AbstractSqlPlatform.d.ts +5 -4
- package/AbstractSqlPlatform.js +34 -5
- package/MonkeyPatchable.d.ts +1 -0
- package/MonkeyPatchable.js +3 -0
- package/PivotCollectionPersister.d.ts +17 -0
- package/PivotCollectionPersister.js +128 -0
- package/README.md +64 -107
- package/SqlEntityManager.d.ts +2 -6
- package/SqlEntityManager.js +5 -9
- package/SqlEntityRepository.d.ts +7 -6
- package/SqlEntityRepository.js +11 -8
- package/index.mjs +24 -5
- package/package.json +8 -8
- package/query/ArrayCriteriaNode.d.ts +4 -3
- package/query/ArrayCriteriaNode.js +5 -0
- package/query/CriteriaNode.d.ts +10 -8
- package/query/CriteriaNode.js +23 -10
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +28 -14
- package/query/ObjectCriteriaNode.d.ts +4 -3
- package/query/ObjectCriteriaNode.js +47 -17
- package/query/QueryBuilder.d.ts +53 -24
- package/query/QueryBuilder.js +393 -133
- package/query/QueryBuilderHelper.d.ts +11 -16
- package/query/QueryBuilderHelper.js +194 -155
- package/query/ScalarCriteriaNode.d.ts +3 -2
- package/query/ScalarCriteriaNode.js +10 -6
- package/query/enums.d.ts +7 -0
- package/query/enums.js +10 -2
- package/schema/DatabaseSchema.d.ts +5 -2
- package/schema/DatabaseSchema.js +28 -10
- package/schema/DatabaseTable.d.ts +13 -5
- package/schema/DatabaseTable.js +384 -60
- package/schema/SchemaComparator.d.ts +5 -5
- package/schema/SchemaComparator.js +59 -35
- package/schema/SchemaHelper.d.ts +6 -4
- package/schema/SchemaHelper.js +19 -8
- package/schema/SqlSchemaGenerator.d.ts +3 -6
- package/schema/SqlSchemaGenerator.js +71 -36
- package/typings.d.ts +19 -10
package/schema/DatabaseTable.js
CHANGED
|
@@ -6,14 +6,19 @@ const core_1 = require("@mikro-orm/core");
|
|
|
6
6
|
* @internal
|
|
7
7
|
*/
|
|
8
8
|
class DatabaseTable {
|
|
9
|
+
platform;
|
|
10
|
+
name;
|
|
11
|
+
schema;
|
|
12
|
+
columns = {};
|
|
13
|
+
indexes = [];
|
|
14
|
+
checks = [];
|
|
15
|
+
foreignKeys = {};
|
|
16
|
+
nativeEnums = {}; // for postgres
|
|
17
|
+
comment;
|
|
9
18
|
constructor(platform, name, schema) {
|
|
10
19
|
this.platform = platform;
|
|
11
20
|
this.name = name;
|
|
12
21
|
this.schema = schema;
|
|
13
|
-
this.columns = {};
|
|
14
|
-
this.indexes = [];
|
|
15
|
-
this.checks = [];
|
|
16
|
-
this.foreignKeys = {};
|
|
17
22
|
Object.defineProperties(this, {
|
|
18
23
|
platform: { enumerable: false, writable: true },
|
|
19
24
|
});
|
|
@@ -24,8 +29,11 @@ class DatabaseTable {
|
|
|
24
29
|
getColumn(name) {
|
|
25
30
|
return this.columns[name];
|
|
26
31
|
}
|
|
32
|
+
removeColumn(name) {
|
|
33
|
+
delete this.columns[name];
|
|
34
|
+
}
|
|
27
35
|
getIndexes() {
|
|
28
|
-
return this.indexes;
|
|
36
|
+
return core_1.Utils.removeDuplicates(this.indexes);
|
|
29
37
|
}
|
|
30
38
|
getChecks() {
|
|
31
39
|
return this.checks;
|
|
@@ -41,7 +49,7 @@ class DatabaseTable {
|
|
|
41
49
|
const type = v.name in enums ? 'enum' : v.type;
|
|
42
50
|
v.mappedType = this.platform.getMappedType(type);
|
|
43
51
|
v.default = v.default?.toString().startsWith('nextval(') ? null : v.default;
|
|
44
|
-
v.enumItems
|
|
52
|
+
v.enumItems ??= enums[v.name] || [];
|
|
45
53
|
o[v.name] = v;
|
|
46
54
|
return o;
|
|
47
55
|
}, {});
|
|
@@ -49,48 +57,55 @@ class DatabaseTable {
|
|
|
49
57
|
addColumn(column) {
|
|
50
58
|
this.columns[column.name] = column;
|
|
51
59
|
}
|
|
52
|
-
addColumnFromProperty(prop, meta) {
|
|
60
|
+
addColumnFromProperty(prop, meta, config) {
|
|
53
61
|
prop.fieldNames.forEach((field, idx) => {
|
|
54
|
-
var _a;
|
|
55
62
|
const type = prop.enum ? 'enum' : prop.columnTypes[idx];
|
|
56
63
|
const mappedType = this.platform.getMappedType(type);
|
|
57
64
|
if (mappedType instanceof core_1.DecimalType) {
|
|
58
65
|
const match = prop.columnTypes[idx].match(/\w+\((\d+), ?(\d+)\)/);
|
|
59
66
|
/* istanbul ignore else */
|
|
60
67
|
if (match) {
|
|
61
|
-
prop.precision
|
|
62
|
-
prop.scale
|
|
68
|
+
prop.precision ??= +match[1];
|
|
69
|
+
prop.scale ??= +match[2];
|
|
63
70
|
prop.length = undefined;
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
if (mappedType instanceof core_1.DateTimeType) {
|
|
67
|
-
|
|
74
|
+
const match = prop.columnTypes[idx].match(/\w+\((\d+)\)/);
|
|
75
|
+
if (match) {
|
|
76
|
+
prop.length ??= +match[1];
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
prop.length ??= this.platform.getDefaultDateTimeLength();
|
|
80
|
+
}
|
|
68
81
|
}
|
|
69
|
-
const primary = !meta.compositePK && !!prop.primary && prop.
|
|
82
|
+
const primary = !meta.compositePK && !!prop.primary && prop.kind === core_1.ReferenceKind.SCALAR && this.platform.isNumericColumn(mappedType);
|
|
70
83
|
this.columns[field] = {
|
|
71
84
|
name: prop.fieldNames[idx],
|
|
72
85
|
type: prop.columnTypes[idx],
|
|
86
|
+
generated: prop.generated,
|
|
73
87
|
mappedType,
|
|
74
88
|
unsigned: prop.unsigned && this.platform.isNumericColumn(mappedType),
|
|
75
89
|
autoincrement: prop.autoincrement ?? primary,
|
|
76
90
|
primary,
|
|
77
|
-
nullable: !!prop.nullable,
|
|
91
|
+
nullable: this.columns[field]?.nullable ?? !!prop.nullable,
|
|
92
|
+
nativeEnumName: prop.nativeEnumName,
|
|
78
93
|
length: prop.length,
|
|
79
94
|
precision: prop.precision,
|
|
80
95
|
scale: prop.scale,
|
|
81
96
|
default: prop.defaultRaw,
|
|
82
|
-
enumItems: prop.items?.every(core_1.Utils.isString) ? prop.items : undefined,
|
|
97
|
+
enumItems: prop.nativeEnumName || prop.items?.every(core_1.Utils.isString) ? prop.items : undefined,
|
|
83
98
|
comment: prop.comment,
|
|
84
99
|
extra: prop.extra,
|
|
85
100
|
ignoreSchemaChanges: prop.ignoreSchemaChanges,
|
|
86
101
|
};
|
|
87
|
-
|
|
102
|
+
this.columns[field].unsigned ||= this.columns[field].autoincrement;
|
|
88
103
|
const defaultValue = this.platform.getSchemaHelper().normalizeDefaultValue(prop.defaultRaw, prop.length);
|
|
89
104
|
this.columns[field].default = defaultValue;
|
|
90
105
|
});
|
|
91
|
-
if ([core_1.
|
|
106
|
+
if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
92
107
|
const constraintName = this.getIndexName(true, prop.fieldNames, 'foreign');
|
|
93
|
-
let schema = prop.targetMeta.schema === '*' ? this.schema : prop.targetMeta.schema ?? this.
|
|
108
|
+
let schema = prop.targetMeta.root.schema === '*' ? this.schema : (prop.targetMeta.root.schema ?? config.get('schema', this.platform.getDefaultSchemaName()));
|
|
94
109
|
if (prop.referencedTableName.includes('.')) {
|
|
95
110
|
schema = undefined;
|
|
96
111
|
}
|
|
@@ -102,11 +117,11 @@ class DatabaseTable {
|
|
|
102
117
|
referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
|
|
103
118
|
};
|
|
104
119
|
const cascade = prop.cascade.includes(core_1.Cascade.REMOVE) || prop.cascade.includes(core_1.Cascade.ALL);
|
|
105
|
-
if (prop.
|
|
106
|
-
this.foreignKeys[constraintName].deleteRule = prop.
|
|
120
|
+
if (prop.deleteRule || cascade || prop.nullable) {
|
|
121
|
+
this.foreignKeys[constraintName].deleteRule = prop.deleteRule || (cascade ? 'cascade' : 'set null');
|
|
107
122
|
}
|
|
108
|
-
if (prop.
|
|
109
|
-
this.foreignKeys[constraintName].updateRule = prop.
|
|
123
|
+
if (prop.updateRule || prop.cascade.includes(core_1.Cascade.PERSIST) || prop.cascade.includes(core_1.Cascade.ALL)) {
|
|
124
|
+
this.foreignKeys[constraintName].updateRule = prop.updateRule || 'cascade';
|
|
110
125
|
}
|
|
111
126
|
}
|
|
112
127
|
if (prop.index) {
|
|
@@ -114,6 +129,7 @@ class DatabaseTable {
|
|
|
114
129
|
columnNames: prop.fieldNames,
|
|
115
130
|
composite: prop.fieldNames.length > 1,
|
|
116
131
|
keyName: this.getIndexName(prop.index, prop.fieldNames, 'index'),
|
|
132
|
+
constraint: false,
|
|
117
133
|
primary: false,
|
|
118
134
|
unique: false,
|
|
119
135
|
});
|
|
@@ -123,6 +139,7 @@ class DatabaseTable {
|
|
|
123
139
|
columnNames: prop.fieldNames,
|
|
124
140
|
composite: prop.fieldNames.length > 1,
|
|
125
141
|
keyName: this.getIndexName(prop.unique, prop.fieldNames, 'unique'),
|
|
142
|
+
constraint: !prop.fieldNames.some((d) => d.includes('.')),
|
|
126
143
|
primary: false,
|
|
127
144
|
unique: true,
|
|
128
145
|
});
|
|
@@ -134,40 +151,284 @@ class DatabaseTable {
|
|
|
134
151
|
}
|
|
135
152
|
return this.platform.getIndexName(this.name, columnNames, type);
|
|
136
153
|
}
|
|
137
|
-
getEntityDeclaration(namingStrategy, schemaHelper) {
|
|
154
|
+
getEntityDeclaration(namingStrategy, schemaHelper, scalarPropertiesForRelations) {
|
|
155
|
+
const { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames, } = this.foreignKeysToProps(namingStrategy, scalarPropertiesForRelations);
|
|
138
156
|
let name = namingStrategy.getClassName(this.name, '_');
|
|
139
157
|
name = name.match(/^\d/) ? 'E' + name : name;
|
|
140
158
|
const schema = new core_1.EntitySchema({ name, collection: this.name, schema: this.schema });
|
|
141
159
|
const compositeFkIndexes = {};
|
|
142
160
|
const compositeFkUniques = {};
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
161
|
+
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.
|
|
162
|
+
&& (index.columnNames.length > 1 // All composite indexes are to be mapped to entity decorators or FK props.
|
|
163
|
+
|| !(index.columnNames[0] in columnFks) // Non-composite indexes for scalar props are to be mapped to the column.
|
|
164
|
+
|| skippedColumnNames.includes(index.columnNames[0]) // Non-composite indexes for skipped columns are to be mapped as entity decorators.
|
|
165
|
+
));
|
|
166
|
+
for (const index of potentiallyUnmappedIndexes) {
|
|
167
|
+
const ret = { name: index.keyName };
|
|
168
|
+
// Index is for FK. Map to the FK prop and move on.
|
|
169
|
+
const fkForIndex = fkIndexes.get(index);
|
|
170
|
+
if (fkForIndex) {
|
|
171
|
+
ret.properties = [this.getPropertyName(namingStrategy, fkForIndex.baseName, fkForIndex.fk)];
|
|
147
172
|
const map = index.unique ? compositeFkUniques : compositeFkIndexes;
|
|
148
173
|
map[ret.properties[0]] = { keyName: index.keyName };
|
|
149
174
|
continue;
|
|
150
175
|
}
|
|
151
|
-
|
|
152
|
-
|
|
176
|
+
const properties = this.getIndexProperties(index, columnFks, fksOnColumnProps, fksOnStandaloneProps, namingStrategy);
|
|
177
|
+
// If there is a column that cannot be unambiguously mapped to a prop, render an expression.
|
|
178
|
+
if (index.expression) {
|
|
179
|
+
ret.expression = index.expression;
|
|
153
180
|
}
|
|
154
|
-
else if (
|
|
155
|
-
|
|
181
|
+
else if (typeof properties === 'undefined') {
|
|
182
|
+
ret.expression = schemaHelper.getCreateIndexSQL(this.name, index);
|
|
156
183
|
}
|
|
157
184
|
else {
|
|
158
|
-
|
|
185
|
+
ret.properties = properties;
|
|
186
|
+
// If the index is for one property that is not a FK prop, map to the column prop and move on.
|
|
187
|
+
if (properties.length === 1 && !fksOnStandaloneProps.has(properties[0])) {
|
|
188
|
+
const map = index.unique ? compositeFkUniques : compositeFkIndexes;
|
|
189
|
+
map[properties[0]] = { keyName: index.keyName };
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Composite indexes that aren't exclusively mapped to FK props get an entity decorator.
|
|
194
|
+
if (index.unique) {
|
|
195
|
+
schema.addUnique(ret);
|
|
196
|
+
continue;
|
|
159
197
|
}
|
|
198
|
+
schema.addIndex(ret);
|
|
160
199
|
}
|
|
161
|
-
|
|
162
|
-
|
|
200
|
+
const addedStandaloneFkPropsBasedOnColumn = new Set;
|
|
201
|
+
const nonSkippedColumns = this.getColumns().filter(column => !skippedColumnNames.includes(column.name));
|
|
202
|
+
for (const column of nonSkippedColumns) {
|
|
203
|
+
const columnName = column.name;
|
|
204
|
+
const standaloneFkPropBasedOnColumn = fksOnStandaloneProps.get(columnName);
|
|
205
|
+
if (standaloneFkPropBasedOnColumn && !fksOnColumnProps.get(columnName)) {
|
|
206
|
+
addedStandaloneFkPropsBasedOnColumn.add(columnName);
|
|
207
|
+
const [fkIndex, currentFk] = standaloneFkPropBasedOnColumn;
|
|
208
|
+
const prop = this.getForeignKeyDeclaration(currentFk, namingStrategy, schemaHelper, fkIndex, nullableForeignKeys.has(currentFk), columnName);
|
|
209
|
+
schema.addProperty(prop.name, prop.type, prop);
|
|
210
|
+
}
|
|
211
|
+
const prop = this.getPropertyDeclaration(column, namingStrategy, schemaHelper, compositeFkIndexes, compositeFkUniques, columnFks, fksOnColumnProps.get(columnName));
|
|
212
|
+
schema.addProperty(prop.name, prop.type, prop);
|
|
213
|
+
}
|
|
214
|
+
for (const [propBaseName, [fkIndex, currentFk]] of fksOnStandaloneProps.entries()) {
|
|
215
|
+
if (addedStandaloneFkPropsBasedOnColumn.has(propBaseName)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const prop = this.getForeignKeyDeclaration(currentFk, namingStrategy, schemaHelper, fkIndex, nullableForeignKeys.has(currentFk), propBaseName);
|
|
163
219
|
schema.addProperty(prop.name, prop.type, prop);
|
|
164
220
|
}
|
|
165
221
|
const meta = schema.init().meta;
|
|
166
|
-
meta.relations
|
|
167
|
-
.filter(prop => prop.primary && prop.
|
|
168
|
-
|
|
222
|
+
const oneToOneCandidateProperties = meta.relations
|
|
223
|
+
.filter(prop => prop.primary && prop.kind === core_1.ReferenceKind.MANY_TO_ONE);
|
|
224
|
+
if (oneToOneCandidateProperties.length === 1
|
|
225
|
+
&& oneToOneCandidateProperties[0].fieldNames.length === (new Set(meta.getPrimaryProps().flatMap(prop => prop.fieldNames))).size) {
|
|
226
|
+
oneToOneCandidateProperties[0].kind = core_1.ReferenceKind.ONE_TO_ONE;
|
|
227
|
+
}
|
|
169
228
|
return meta;
|
|
170
229
|
}
|
|
230
|
+
foreignKeysToProps(namingStrategy, scalarPropertiesForRelations) {
|
|
231
|
+
const fks = Object.values(this.getForeignKeys());
|
|
232
|
+
const fksOnColumnProps = new Map();
|
|
233
|
+
const fksOnStandaloneProps = new Map();
|
|
234
|
+
const columnFks = {};
|
|
235
|
+
const fkIndexes = new Map();
|
|
236
|
+
const nullableForeignKeys = new Set();
|
|
237
|
+
for (const currentFk of fks) {
|
|
238
|
+
const fkIndex = this.findFkIndex(currentFk);
|
|
239
|
+
if (currentFk.columnNames.length === 1 && !fks.some(fk => fk !== currentFk && fk.columnNames.length === 1 && currentFk.columnNames[0] === fk.columnNames[0])) {
|
|
240
|
+
// Non-composite FK is the only possible one for a column. Render the column with it.
|
|
241
|
+
const columnName = currentFk.columnNames[0];
|
|
242
|
+
columnFks[columnName] ??= [];
|
|
243
|
+
columnFks[columnName].push(currentFk);
|
|
244
|
+
if (this.getColumn(columnName)?.nullable) {
|
|
245
|
+
nullableForeignKeys.add(currentFk);
|
|
246
|
+
}
|
|
247
|
+
if (scalarPropertiesForRelations === 'always') {
|
|
248
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName);
|
|
249
|
+
fksOnStandaloneProps.set(baseName, [fkIndex, currentFk]);
|
|
250
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
fksOnColumnProps.set(columnName, currentFk);
|
|
254
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName: columnName });
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const specificColumnNames = [];
|
|
259
|
+
const nullableColumnsInFk = [];
|
|
260
|
+
for (const columnName of currentFk.columnNames) {
|
|
261
|
+
columnFks[columnName] ??= [];
|
|
262
|
+
columnFks[columnName].push(currentFk);
|
|
263
|
+
if (!fks.some(fk => fk !== currentFk && fk.columnNames.includes(columnName))) {
|
|
264
|
+
specificColumnNames.push(columnName);
|
|
265
|
+
}
|
|
266
|
+
if (this.getColumn(columnName)?.nullable) {
|
|
267
|
+
nullableColumnsInFk.push(columnName);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (nullableColumnsInFk.length > 0) {
|
|
271
|
+
nullableForeignKeys.add(currentFk);
|
|
272
|
+
}
|
|
273
|
+
if (specificColumnNames.length === 1 && ((nullableColumnsInFk.length === currentFk.columnNames.length || nullableColumnsInFk.length === 0) || (nullableColumnsInFk.length === 1 && nullableColumnsInFk[0] === specificColumnNames[0]))) {
|
|
274
|
+
// Composite FK has exactly one column which is not used in any other FK.
|
|
275
|
+
// The FK also doesn't have a mix of nullable and non-nullable columns,
|
|
276
|
+
// or its only nullable column is this very one.
|
|
277
|
+
// It is safe to just render this FK attached to the specific column.
|
|
278
|
+
const columnName = specificColumnNames[0];
|
|
279
|
+
if (scalarPropertiesForRelations === 'always') {
|
|
280
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName);
|
|
281
|
+
fksOnStandaloneProps.set(baseName, [fkIndex, currentFk]);
|
|
282
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
fksOnColumnProps.set(columnName, currentFk);
|
|
286
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName: columnName });
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (specificColumnNames.length === currentFk.columnNames.length) {
|
|
291
|
+
// All columns involved with this FK are only covered by this one FK.
|
|
292
|
+
if (nullableColumnsInFk.length <= 1) {
|
|
293
|
+
// Also, this FK is either not nullable, or has only one nullable column.
|
|
294
|
+
// It is safe to name the FK after the nullable column, or any non-nullable one (the first one is picked).
|
|
295
|
+
const columnName = nullableColumnsInFk.at(0) ?? currentFk.columnNames[0];
|
|
296
|
+
if (scalarPropertiesForRelations === 'always') {
|
|
297
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName);
|
|
298
|
+
fksOnStandaloneProps.set(baseName, [fkIndex, currentFk]);
|
|
299
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
fksOnColumnProps.set(columnName, currentFk);
|
|
303
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName: columnName });
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
// If the first nullable column's name with FK is different from the name without FK,
|
|
308
|
+
// name a standalone prop after the column, but treat the column prop itself as not having FK.
|
|
309
|
+
const columnName = nullableColumnsInFk[0];
|
|
310
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName);
|
|
311
|
+
fksOnStandaloneProps.set(baseName, [fkIndex, currentFk]);
|
|
312
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
// FK is not unambiguously mappable to a column. Pick another name for a standalone FK prop.
|
|
316
|
+
const baseName = this.getSafeBaseNameForFkProp(namingStrategy, currentFk, fks);
|
|
317
|
+
fksOnStandaloneProps.set(baseName, [fkIndex, currentFk]);
|
|
318
|
+
fkIndexes.set(fkIndex, { fk: currentFk, baseName });
|
|
319
|
+
}
|
|
320
|
+
const columnsInFks = Object.keys(columnFks);
|
|
321
|
+
const skippingHandlers = {
|
|
322
|
+
// Never generate scalar props for composite keys,
|
|
323
|
+
// i.e. always skip columns if they are covered by foreign keys.
|
|
324
|
+
never: (column) => columnsInFks.includes(column.name) && !fksOnColumnProps.has(column.name),
|
|
325
|
+
// Always generate scalar props for composite keys,
|
|
326
|
+
// i.e. do not skip columns, even if they are covered by foreign keys.
|
|
327
|
+
always: (column) => false,
|
|
328
|
+
// Smart scalar props generation.
|
|
329
|
+
// Skips columns if they are covered by foreign keys.
|
|
330
|
+
// But also does not skip if the column is not nullable, and yet all involved FKs are nullable,
|
|
331
|
+
// or if one or more FKs involved has multiple nullable columns.
|
|
332
|
+
smart: (column) => {
|
|
333
|
+
return columnsInFks.includes(column.name)
|
|
334
|
+
&& !fksOnColumnProps.has(column.name)
|
|
335
|
+
&& (column.nullable
|
|
336
|
+
? columnFks[column.name].some(fk => !fk.columnNames.some(fkColumnName => fkColumnName !== column.name && this.getColumn(fkColumnName)?.nullable))
|
|
337
|
+
: columnFks[column.name].some(fk => !nullableForeignKeys.has(fk)));
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
const skippedColumnNames = this.getColumns().filter(skippingHandlers[scalarPropertiesForRelations]).map(column => column.name);
|
|
341
|
+
return { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames };
|
|
342
|
+
}
|
|
343
|
+
findFkIndex(currentFk) {
|
|
344
|
+
const fkColumnsLength = currentFk.columnNames.length;
|
|
345
|
+
const possibleIndexes = this.indexes.filter(index => {
|
|
346
|
+
return index.columnNames.length >= fkColumnsLength && !currentFk.columnNames.some((columnName, i) => index.columnNames[i] !== columnName);
|
|
347
|
+
});
|
|
348
|
+
possibleIndexes.sort((a, b) => {
|
|
349
|
+
if (a.primary !== b.primary) {
|
|
350
|
+
return a.primary ? -1 : 1;
|
|
351
|
+
}
|
|
352
|
+
if (a.unique !== b.unique) {
|
|
353
|
+
return a.unique ? -1 : 1;
|
|
354
|
+
}
|
|
355
|
+
if (a.columnNames.length !== b.columnNames.length) {
|
|
356
|
+
return a.columnNames.length < b.columnNames.length ? -1 : 1;
|
|
357
|
+
}
|
|
358
|
+
return a.keyName.localeCompare(b.keyName);
|
|
359
|
+
});
|
|
360
|
+
return possibleIndexes[0];
|
|
361
|
+
}
|
|
362
|
+
getIndexProperties(index, columnFks, fksOnColumnProps, fksOnStandaloneProps, namingStrategy) {
|
|
363
|
+
const propBaseNames = new Set();
|
|
364
|
+
const columnNames = index.columnNames;
|
|
365
|
+
const l = columnNames.length;
|
|
366
|
+
for (let i = 0; i < l; ++i) {
|
|
367
|
+
const columnName = columnNames[i];
|
|
368
|
+
// The column is not involved with FKs.
|
|
369
|
+
// It has a prop named after it.
|
|
370
|
+
// Add it and move on.
|
|
371
|
+
if (!(columnName in columnFks)) {
|
|
372
|
+
propBaseNames.add(columnName);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
// If the prop named after the column has a FK and the FK's columns are a subset of this index,
|
|
376
|
+
// include this prop and move on.
|
|
377
|
+
const columnPropFk = fksOnColumnProps.get(columnName);
|
|
378
|
+
if (columnPropFk && !columnPropFk.columnNames.some(fkColumnName => {
|
|
379
|
+
return !columnNames.includes(fkColumnName);
|
|
380
|
+
})) {
|
|
381
|
+
propBaseNames.add(columnName);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
// If there is at least one standalone FK featuring this column,
|
|
385
|
+
// and all of its columns are a subset of this index,
|
|
386
|
+
// include that FK, and consider mapping of this column to a prop a success.
|
|
387
|
+
let propAdded = false;
|
|
388
|
+
for (const [propName, [, fk]] of fksOnStandaloneProps) {
|
|
389
|
+
if (!columnFks[columnName].includes(fk)) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (!fk.columnNames.some(fkColumnName => {
|
|
393
|
+
return !columnNames.includes(fkColumnName);
|
|
394
|
+
})) {
|
|
395
|
+
propBaseNames.add(propName);
|
|
396
|
+
propAdded = true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (propAdded) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
// If we have reached this point, it means the column is not mappable to a prop name.
|
|
403
|
+
// Break the whole prop creation.
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
return Array.from(propBaseNames).map(baseName => this.getPropertyName(namingStrategy, baseName, fksOnColumnProps.get(baseName)));
|
|
407
|
+
}
|
|
408
|
+
getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName) {
|
|
409
|
+
if (columnName && this.getPropertyName(namingStrategy, columnName, currentFk) !== this.getPropertyName(namingStrategy, columnName)) {
|
|
410
|
+
// The eligible scalar column name is different from the name of the FK prop of the same column.
|
|
411
|
+
// Both can be safely rendered.
|
|
412
|
+
// Use the column name as a base for the FK prop.
|
|
413
|
+
return columnName;
|
|
414
|
+
}
|
|
415
|
+
if (!fks.some(fk => fk !== currentFk && fk.referencedTableName === currentFk.referencedTableName) && !this.getColumn(currentFk.referencedTableName)) {
|
|
416
|
+
// FK is the only one in this table that references this other table.
|
|
417
|
+
// The name of the referenced table is not shared with a column in this table,
|
|
418
|
+
// so it is safe to output prop name based on the referenced entity.
|
|
419
|
+
return currentFk.referencedTableName;
|
|
420
|
+
}
|
|
421
|
+
// Any ambiguous FK is rendered with a name based on the FK constraint name
|
|
422
|
+
let finalPropBaseName = currentFk.constraintName;
|
|
423
|
+
while (this.getColumn(finalPropBaseName)) {
|
|
424
|
+
// In the unlikely event that the FK constraint name is shared by a column name, generate a name by
|
|
425
|
+
// continuously prefixing with "fk_", until a non-existent column is hit.
|
|
426
|
+
// The worst case scenario is a very long name with several repeated "fk_"
|
|
427
|
+
// that is not really a valid DB identifier but a valid JS variable name.
|
|
428
|
+
finalPropBaseName = `fk_${finalPropBaseName}`;
|
|
429
|
+
}
|
|
430
|
+
return finalPropBaseName;
|
|
431
|
+
}
|
|
171
432
|
/**
|
|
172
433
|
* The shortest name is stripped of the default namespace. All other namespaced elements are returned as full-qualified names.
|
|
173
434
|
*/
|
|
@@ -201,61 +462,108 @@ class DatabaseTable {
|
|
|
201
462
|
hasPrimaryKey() {
|
|
202
463
|
return !!this.getPrimaryKey();
|
|
203
464
|
}
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
const
|
|
465
|
+
getForeignKeyDeclaration(fk, namingStrategy, schemaHelper, fkIndex, nullable, propNameBase) {
|
|
466
|
+
const prop = this.getPropertyName(namingStrategy, propNameBase, fk);
|
|
467
|
+
const kind = (fkIndex.unique && !fkIndex.primary) ? this.getReferenceKind(fk, fkIndex) : this.getReferenceKind(fk);
|
|
468
|
+
const type = this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
469
|
+
const fkOptions = {};
|
|
470
|
+
fkOptions.fieldNames = fk.columnNames;
|
|
471
|
+
fkOptions.referencedTableName = fk.referencedTableName;
|
|
472
|
+
fkOptions.referencedColumnNames = fk.referencedColumnNames;
|
|
473
|
+
fkOptions.updateRule = fk.updateRule?.toLowerCase();
|
|
474
|
+
fkOptions.deleteRule = fk.deleteRule?.toLowerCase();
|
|
475
|
+
const columnOptions = {};
|
|
476
|
+
if (fk.columnNames.length === 1) {
|
|
477
|
+
const column = this.getColumn(fk.columnNames[0]);
|
|
478
|
+
columnOptions.default = this.getPropertyDefaultValue(schemaHelper, column, type);
|
|
479
|
+
columnOptions.defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, type, true);
|
|
480
|
+
columnOptions.nullable = column.nullable;
|
|
481
|
+
columnOptions.primary = column.primary;
|
|
482
|
+
columnOptions.length = column.length;
|
|
483
|
+
columnOptions.precision = column.precision;
|
|
484
|
+
columnOptions.scale = column.scale;
|
|
485
|
+
columnOptions.enum = !!column.enumItems?.length;
|
|
486
|
+
columnOptions.items = column.enumItems;
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
name: prop,
|
|
490
|
+
type,
|
|
491
|
+
kind,
|
|
492
|
+
...columnOptions,
|
|
493
|
+
nullable,
|
|
494
|
+
primary: fkIndex.primary || !fk.columnNames.some(columnName => !this.getPrimaryKey()?.columnNames.includes(columnName)),
|
|
495
|
+
index: !fkIndex.unique ? fkIndex.keyName : undefined,
|
|
496
|
+
unique: (fkIndex.unique && !fkIndex.primary) ? fkIndex.keyName : undefined,
|
|
497
|
+
...fkOptions,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
getPropertyDeclaration(column, namingStrategy, schemaHelper, compositeFkIndexes, compositeFkUniques, columnFks, fk) {
|
|
501
|
+
const prop = this.getPropertyName(namingStrategy, column.name, fk);
|
|
502
|
+
const persist = !(column.name in columnFks && typeof fk === 'undefined');
|
|
207
503
|
const index = compositeFkIndexes[prop] || this.indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && !idx.unique && !idx.primary);
|
|
208
504
|
const unique = compositeFkUniques[prop] || this.indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && idx.unique && !idx.primary);
|
|
209
|
-
const
|
|
210
|
-
const type = this.
|
|
505
|
+
const kind = this.getReferenceKind(fk, unique);
|
|
506
|
+
const type = this.getPropertyTypeForColumn(namingStrategy, column, fk);
|
|
211
507
|
const fkOptions = {};
|
|
212
508
|
if (fk) {
|
|
213
509
|
fkOptions.fieldNames = fk.columnNames;
|
|
214
510
|
fkOptions.referencedTableName = fk.referencedTableName;
|
|
215
511
|
fkOptions.referencedColumnNames = fk.referencedColumnNames;
|
|
216
|
-
fkOptions.
|
|
217
|
-
fkOptions.
|
|
512
|
+
fkOptions.updateRule = fk.updateRule?.toLowerCase();
|
|
513
|
+
fkOptions.deleteRule = fk.deleteRule?.toLowerCase();
|
|
218
514
|
}
|
|
219
515
|
return {
|
|
220
516
|
name: prop,
|
|
221
517
|
type,
|
|
222
|
-
|
|
518
|
+
kind,
|
|
223
519
|
columnType: column.type,
|
|
224
520
|
default: this.getPropertyDefaultValue(schemaHelper, column, type),
|
|
225
521
|
defaultRaw: this.getPropertyDefaultValue(schemaHelper, column, type, true),
|
|
226
522
|
nullable: column.nullable,
|
|
227
|
-
primary: column.primary,
|
|
523
|
+
primary: column.primary && persist,
|
|
228
524
|
fieldName: column.name,
|
|
229
525
|
length: column.length,
|
|
526
|
+
precision: column.precision,
|
|
527
|
+
scale: column.scale,
|
|
230
528
|
index: index ? index.keyName : undefined,
|
|
231
529
|
unique: unique ? unique.keyName : undefined,
|
|
232
530
|
enum: !!column.enumItems?.length,
|
|
233
531
|
items: column.enumItems,
|
|
532
|
+
persist,
|
|
234
533
|
...fkOptions,
|
|
235
534
|
};
|
|
236
535
|
}
|
|
237
|
-
|
|
536
|
+
getReferenceKind(fk, unique) {
|
|
238
537
|
if (fk && unique) {
|
|
239
|
-
return core_1.
|
|
538
|
+
return core_1.ReferenceKind.ONE_TO_ONE;
|
|
240
539
|
}
|
|
241
540
|
if (fk) {
|
|
242
|
-
return core_1.
|
|
541
|
+
return core_1.ReferenceKind.MANY_TO_ONE;
|
|
243
542
|
}
|
|
244
|
-
return core_1.
|
|
543
|
+
return core_1.ReferenceKind.SCALAR;
|
|
245
544
|
}
|
|
246
|
-
getPropertyName(namingStrategy,
|
|
247
|
-
|
|
248
|
-
let field = column.name;
|
|
545
|
+
getPropertyName(namingStrategy, baseName, fk) {
|
|
546
|
+
let field = baseName;
|
|
249
547
|
if (fk) {
|
|
250
|
-
const idx = fk.columnNames.indexOf(
|
|
251
|
-
|
|
548
|
+
const idx = fk.columnNames.indexOf(baseName);
|
|
549
|
+
let replacedFieldName = field.replace(new RegExp(`_${fk.referencedColumnNames[idx]}$`), '');
|
|
550
|
+
if (replacedFieldName === field) {
|
|
551
|
+
replacedFieldName = field.replace(new RegExp(`_${namingStrategy.referenceColumnName()}$`), '');
|
|
552
|
+
}
|
|
553
|
+
field = replacedFieldName;
|
|
554
|
+
}
|
|
555
|
+
if (field.startsWith('_')) {
|
|
556
|
+
return field;
|
|
252
557
|
}
|
|
253
558
|
return namingStrategy.columnNameToProperty(field);
|
|
254
559
|
}
|
|
255
|
-
|
|
560
|
+
getPropertyTypeForForeignKey(namingStrategy, fk) {
|
|
561
|
+
const parts = fk.referencedTableName.split('.', 2);
|
|
562
|
+
return namingStrategy.getClassName(parts.length > 1 ? parts[1] : parts[0], '_');
|
|
563
|
+
}
|
|
564
|
+
getPropertyTypeForColumn(namingStrategy, column, fk) {
|
|
256
565
|
if (fk) {
|
|
257
|
-
|
|
258
|
-
return namingStrategy.getClassName(parts.length > 1 ? parts[1] : parts[0], '_');
|
|
566
|
+
return this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
259
567
|
}
|
|
260
568
|
// If this column is using an enum.
|
|
261
569
|
if (column.enumItems?.length) {
|
|
@@ -288,7 +596,20 @@ class DatabaseTable {
|
|
|
288
596
|
return '' + val;
|
|
289
597
|
}
|
|
290
598
|
addIndex(meta, index, type) {
|
|
291
|
-
const properties = core_1.Utils.flatten(core_1.Utils.asArray(index.properties).map(prop =>
|
|
599
|
+
const properties = core_1.Utils.unique(core_1.Utils.flatten(core_1.Utils.asArray(index.properties).map(prop => {
|
|
600
|
+
const root = prop.replace(/\..+$/, '');
|
|
601
|
+
if (meta.properties[prop]) {
|
|
602
|
+
if (meta.properties[prop].embeddedPath) {
|
|
603
|
+
return [meta.properties[prop].embeddedPath.join('.')];
|
|
604
|
+
}
|
|
605
|
+
return meta.properties[prop].fieldNames;
|
|
606
|
+
}
|
|
607
|
+
// json index, we need to rename the column only
|
|
608
|
+
if (meta.properties[root]) {
|
|
609
|
+
return [prop.replace(root, meta.properties[root].fieldNames[0])];
|
|
610
|
+
}
|
|
611
|
+
return [prop];
|
|
612
|
+
})));
|
|
292
613
|
if (properties.length === 0 && !index.expression) {
|
|
293
614
|
return;
|
|
294
615
|
}
|
|
@@ -297,10 +618,13 @@ class DatabaseTable {
|
|
|
297
618
|
keyName: name,
|
|
298
619
|
columnNames: properties,
|
|
299
620
|
composite: properties.length > 1,
|
|
621
|
+
// JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
|
|
622
|
+
constraint: type !== 'index' && !properties.some((d) => d.includes('.')),
|
|
300
623
|
primary: type === 'primary',
|
|
301
624
|
unique: type !== 'index',
|
|
302
625
|
type: index.type,
|
|
303
626
|
expression: index.expression,
|
|
627
|
+
options: index.options,
|
|
304
628
|
});
|
|
305
629
|
}
|
|
306
630
|
addCheck(check) {
|
|
@@ -308,10 +632,10 @@ class DatabaseTable {
|
|
|
308
632
|
}
|
|
309
633
|
toJSON() {
|
|
310
634
|
const { platform, columns, ...rest } = this;
|
|
311
|
-
const columnsMapped =
|
|
635
|
+
const columnsMapped = core_1.Utils.keys(columns).reduce((o, col) => {
|
|
312
636
|
const { mappedType, ...restCol } = columns[col];
|
|
313
637
|
o[col] = restCol;
|
|
314
|
-
o[col].mappedType =
|
|
638
|
+
o[col].mappedType = core_1.Utils.keys(core_1.t).find(k => core_1.t[k] === mappedType.constructor);
|
|
315
639
|
return o;
|
|
316
640
|
}, {});
|
|
317
641
|
return { columns: columnsMapped, ...rest };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Column, ForeignKey, IndexDef, SchemaDifference, TableDifference } from '../typings';
|
|
2
2
|
import type { DatabaseSchema } from './DatabaseSchema';
|
|
3
3
|
import type { DatabaseTable } from './DatabaseTable';
|
|
4
4
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform';
|
|
@@ -17,12 +17,12 @@ export declare class SchemaComparator {
|
|
|
17
17
|
* operations to change the schema stored in fromSchema to the schema that is
|
|
18
18
|
* stored in toSchema.
|
|
19
19
|
*/
|
|
20
|
-
compare(fromSchema: DatabaseSchema, toSchema: DatabaseSchema): SchemaDifference;
|
|
20
|
+
compare(fromSchema: DatabaseSchema, toSchema: DatabaseSchema, inverseDiff?: SchemaDifference): SchemaDifference;
|
|
21
21
|
/**
|
|
22
22
|
* Returns the difference between the tables fromTable and toTable.
|
|
23
23
|
* If there are no differences this method returns the boolean false.
|
|
24
24
|
*/
|
|
25
|
-
diffTable(fromTable: DatabaseTable, toTable: DatabaseTable): TableDifference | false;
|
|
25
|
+
diffTable(fromTable: DatabaseTable, toTable: DatabaseTable, inverseTableDiff?: TableDifference): TableDifference | false;
|
|
26
26
|
/**
|
|
27
27
|
* Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
|
|
28
28
|
* however ambiguities between different possibilities should not lead to renaming at all.
|
|
@@ -38,7 +38,7 @@ export declare class SchemaComparator {
|
|
|
38
38
|
* Returns the difference between the columns
|
|
39
39
|
* If there are differences this method returns field2, otherwise the boolean false.
|
|
40
40
|
*/
|
|
41
|
-
diffColumn(
|
|
41
|
+
diffColumn(fromColumn: Column, toColumn: Column, tableName?: string): Set<string>;
|
|
42
42
|
diffEnumItems(items1?: string[], items2?: string[]): boolean;
|
|
43
43
|
diffComment(comment1?: string, comment2?: string): boolean;
|
|
44
44
|
/**
|
|
@@ -50,7 +50,7 @@ export declare class SchemaComparator {
|
|
|
50
50
|
* Checks if the other index already fulfills all the indexing and constraint needs of the current one.
|
|
51
51
|
*/
|
|
52
52
|
isIndexFulfilledBy(index1: IndexDef, index2: IndexDef): boolean;
|
|
53
|
-
|
|
53
|
+
diffExpression(expr1: string, expr2: string): boolean;
|
|
54
54
|
hasSameDefaultValue(from: Column, to: Column): boolean;
|
|
55
55
|
private mapColumnToProperty;
|
|
56
56
|
private log;
|