@mikro-orm/knex 6.0.0-dev.25 → 6.0.0-dev.250

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