@mikro-orm/knex 6.0.0-dev.23 → 6.0.0-dev.230

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 +576 -269
  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 +24 -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 -6
  43. package/schema/SqlSchemaGenerator.js +71 -36
  44. package/typings.d.ts +30 -11
@@ -1,4 +1,5 @@
1
- import type { CheckDef, Column, ForeignKey, IndexDef, SchemaDifference, TableDifference } from '../typings';
1
+ import { type Dictionary } from '@mikro-orm/core';
2
+ import type { Column, ForeignKey, IndexDef, SchemaDifference, TableDifference } from '../typings';
2
3
  import type { DatabaseSchema } from './DatabaseSchema';
3
4
  import type { DatabaseTable } from './DatabaseTable';
4
5
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform';
@@ -17,12 +18,12 @@ export declare class SchemaComparator {
17
18
  * operations to change the schema stored in fromSchema to the schema that is
18
19
  * stored in toSchema.
19
20
  */
20
- compare(fromSchema: DatabaseSchema, toSchema: DatabaseSchema): SchemaDifference;
21
+ compare(fromSchema: DatabaseSchema, toSchema: DatabaseSchema, inverseDiff?: SchemaDifference): SchemaDifference;
21
22
  /**
22
23
  * Returns the difference between the tables fromTable and toTable.
23
24
  * If there are no differences this method returns the boolean false.
24
25
  */
25
- diffTable(fromTable: DatabaseTable, toTable: DatabaseTable): TableDifference | false;
26
+ diffTable(fromTable: DatabaseTable, toTable: DatabaseTable, inverseTableDiff?: TableDifference): TableDifference | false;
26
27
  /**
27
28
  * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
28
29
  * however ambiguities between different possibilities should not lead to renaming at all.
@@ -38,7 +39,7 @@ export declare class SchemaComparator {
38
39
  * Returns the difference between the columns
39
40
  * If there are differences this method returns field2, otherwise the boolean false.
40
41
  */
41
- diffColumn(column1: Column, column2: Column, tableName?: string): Set<string>;
42
+ diffColumn(fromColumn: Column, toColumn: Column, tableName?: string): Set<string>;
42
43
  diffEnumItems(items1?: string[], items2?: string[]): boolean;
43
44
  diffComment(comment1?: string, comment2?: string): boolean;
44
45
  /**
@@ -50,7 +51,8 @@ export declare class SchemaComparator {
50
51
  * Checks if the other index already fulfills all the indexing and constraint needs of the current one.
51
52
  */
52
53
  isIndexFulfilledBy(index1: IndexDef, index2: IndexDef): boolean;
53
- diffCheck(check1: CheckDef, check2: CheckDef): boolean;
54
+ diffExpression(expr1: string, expr2: string): boolean;
55
+ parseJsonDefault(defaultValue?: string | null): Dictionary | string | null;
54
56
  hasSameDefaultValue(from: Column, to: Column): boolean;
55
57
  private mapColumnToProperty;
56
58
  private log;
@@ -7,6 +7,9 @@ const core_1 = require("@mikro-orm/core");
7
7
  * Compares two Schemas and return an instance of SchemaDifference.
8
8
  */
9
9
  class SchemaComparator {
10
+ platform;
11
+ helper;
12
+ logger;
10
13
  constructor(platform) {
11
14
  this.platform = platform;
12
15
  this.helper = this.platform.getSchemaHelper();
@@ -19,7 +22,7 @@ class SchemaComparator {
19
22
  * operations to change the schema stored in fromSchema to the schema that is
20
23
  * stored in toSchema.
21
24
  */
22
- compare(fromSchema, toSchema) {
25
+ compare(fromSchema, toSchema, inverseDiff) {
23
26
  const diff = { newTables: {}, removedTables: {}, changedTables: {}, orphanedForeignKeys: [], newNamespaces: new Set(), removedNamespaces: new Set(), fromSchema };
24
27
  const foreignKeysToTable = {};
25
28
  for (const namespace of toSchema.getNamespaces()) {
@@ -40,7 +43,7 @@ class SchemaComparator {
40
43
  diff.newTables[tableName] = toSchema.getTable(tableName);
41
44
  }
42
45
  else {
43
- const tableDifferences = this.diffTable(fromSchema.getTable(tableName), toSchema.getTable(tableName));
46
+ const tableDifferences = this.diffTable(fromSchema.getTable(tableName), toSchema.getTable(tableName), inverseDiff?.changedTables[tableName]);
44
47
  if (tableDifferences !== false) {
45
48
  diff.changedTables[tableName] = tableDifferences;
46
49
  }
@@ -88,7 +91,7 @@ class SchemaComparator {
88
91
  * Returns the difference between the tables fromTable and toTable.
89
92
  * If there are no differences this method returns the boolean false.
90
93
  */
91
- diffTable(fromTable, toTable) {
94
+ diffTable(fromTable, toTable, inverseTableDiff) {
92
95
  let changes = 0;
93
96
  const tableDifferences = {
94
97
  name: fromTable.getShortestName(),
@@ -139,6 +142,12 @@ class SchemaComparator {
139
142
  if (changedProperties.size === 0) {
140
143
  continue;
141
144
  }
145
+ if (changedProperties.size === 1 && changedProperties.has('generated')) {
146
+ tableDifferences.addedColumns[column.name] = toTable.getColumn(column.name);
147
+ tableDifferences.removedColumns[column.name] = column;
148
+ changes++;
149
+ continue;
150
+ }
142
151
  tableDifferences.changedColumns[column.name] = {
143
152
  oldColumnName: column.name,
144
153
  fromColumn: column,
@@ -148,7 +157,7 @@ class SchemaComparator {
148
157
  this.log(`column ${tableDifferences.name}.${column.name} changed`, { changedProperties });
149
158
  changes++;
150
159
  }
151
- this.detectColumnRenamings(tableDifferences);
160
+ this.detectColumnRenamings(tableDifferences, inverseTableDiff);
152
161
  const fromTableIndexes = fromTable.getIndexes();
153
162
  const toTableIndexes = toTable.getIndexes();
154
163
  // See if all the indexes in "from" table exist in "to" table
@@ -200,7 +209,7 @@ class SchemaComparator {
200
209
  }
201
210
  // See if index has changed in "to" table
202
211
  const toTableCheck = toTable.getCheck(check.name);
203
- if (!this.diffCheck(check, toTableCheck)) {
212
+ if (!this.diffExpression(check.expression, toTableCheck.expression)) {
204
213
  continue;
205
214
  }
206
215
  this.log(`check constraint ${check.name} changed in table ${tableDifferences.name}`, { fromTableCheck: check, toTableCheck });
@@ -240,7 +249,7 @@ class SchemaComparator {
240
249
  * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
241
250
  * however ambiguities between different possibilities should not lead to renaming at all.
242
251
  */
243
- detectColumnRenamings(tableDifferences) {
252
+ detectColumnRenamings(tableDifferences, inverseTableDiff) {
244
253
  const renameCandidates = {};
245
254
  for (const addedColumn of Object.values(tableDifferences.addedColumns)) {
246
255
  for (const removedColumn of Object.values(tableDifferences.removedColumns)) {
@@ -248,6 +257,10 @@ class SchemaComparator {
248
257
  if (diff.size !== 0) {
249
258
  continue;
250
259
  }
260
+ const renamedColumn = inverseTableDiff?.renamedColumns[addedColumn.name];
261
+ if (renamedColumn && renamedColumn?.name !== removedColumn.name) {
262
+ continue;
263
+ }
251
264
  renameCandidates[addedColumn.name] = renameCandidates[addedColumn.name] ?? [];
252
265
  renameCandidates[addedColumn.name].push([removedColumn, addedColumn]);
253
266
  }
@@ -324,12 +337,12 @@ class SchemaComparator {
324
337
  * Returns the difference between the columns
325
338
  * If there are differences this method returns field2, otherwise the boolean false.
326
339
  */
327
- diffColumn(column1, column2, tableName) {
340
+ diffColumn(fromColumn, toColumn, tableName) {
328
341
  const changedProperties = new Set();
329
- const prop1 = this.mapColumnToProperty({ ...column1, autoincrement: false });
330
- const prop2 = this.mapColumnToProperty({ ...column2, autoincrement: false });
331
- const columnType1 = column1.mappedType.getColumnType(prop1, this.platform).toLowerCase();
332
- const columnType2 = column2.mappedType.getColumnType(prop2, this.platform).toLowerCase();
342
+ const fromProp = this.mapColumnToProperty({ ...fromColumn, autoincrement: false });
343
+ const toProp = this.mapColumnToProperty({ ...toColumn, autoincrement: false });
344
+ const fromColumnType = fromColumn.mappedType.getColumnType(fromProp, this.platform).toLowerCase();
345
+ const toColumnType = toColumn.mappedType.getColumnType(toProp, this.platform).toLowerCase();
333
346
  const log = (msg, params) => {
334
347
  if (tableName) {
335
348
  const copy = core_1.Utils.copy(params);
@@ -337,40 +350,46 @@ class SchemaComparator {
337
350
  this.log(msg, copy);
338
351
  }
339
352
  };
340
- if (columnType1 !== columnType2 &&
341
- !(column1.ignoreSchemaChanges?.includes('type') ||
342
- column2.ignoreSchemaChanges?.includes('type'))) {
343
- log(`'type' changed for column ${tableName}.${column1.name}`, { columnType1, columnType2 });
353
+ if (fromColumnType !== toColumnType &&
354
+ !(fromColumn.ignoreSchemaChanges?.includes('type') ||
355
+ toColumn.ignoreSchemaChanges?.includes('type'))) {
356
+ log(`'type' changed for column ${tableName}.${fromColumn.name}`, { fromColumnType, toColumnType });
344
357
  changedProperties.add('type');
345
358
  }
346
- if (column1.nullable !== column2.nullable) {
347
- log(`'nullable' changed for column ${tableName}.${column1.name}`, { column1, column2 });
359
+ if (fromColumn.nullable !== toColumn.nullable && !fromColumn.generated && !toColumn.generated) {
360
+ log(`'nullable' changed for column ${tableName}.${fromColumn.name}`, { fromColumn, toColumn });
348
361
  changedProperties.add('nullable');
349
362
  }
350
- if (!!column1.autoincrement !== !!column2.autoincrement) {
351
- log(`'autoincrement' changed for column ${tableName}.${column1.name}`, { column1, column2 });
363
+ if (this.diffExpression(fromColumn.generated, toColumn.generated)) {
364
+ log(`'generated' changed for column ${tableName}.${fromColumn.name}`, { fromColumn, toColumn });
365
+ changedProperties.add('generated');
366
+ }
367
+ if (!!fromColumn.autoincrement !== !!toColumn.autoincrement) {
368
+ log(`'autoincrement' changed for column ${tableName}.${fromColumn.name}`, { fromColumn, toColumn });
352
369
  changedProperties.add('autoincrement');
353
370
  }
354
- if (column1.unsigned !== column2.unsigned && this.platform.supportsUnsigned()) {
355
- log(`'unsigned' changed for column ${tableName}.${column1.name}`, { column1, column2 });
371
+ if (fromColumn.unsigned !== toColumn.unsigned && this.platform.supportsUnsigned()) {
372
+ log(`'unsigned' changed for column ${tableName}.${fromColumn.name}`, { fromColumn, toColumn });
356
373
  changedProperties.add('unsigned');
357
374
  }
358
- if (!this.hasSameDefaultValue(column1, column2)) {
359
- log(`'default' changed for column ${tableName}.${column1.name}`, { column1, column2 });
375
+ if (!this.hasSameDefaultValue(fromColumn, toColumn)) {
376
+ log(`'default' changed for column ${tableName}.${fromColumn.name}`, { fromColumn, toColumn });
360
377
  changedProperties.add('default');
361
378
  }
362
- if (this.diffComment(column1.comment, column2.comment)) {
363
- log(`'comment' changed for column ${tableName}.${column1.name}`, { column1, column2 });
379
+ if (this.diffComment(fromColumn.comment, toColumn.comment)) {
380
+ log(`'comment' changed for column ${tableName}.${fromColumn.name}`, { fromColumn, toColumn });
364
381
  changedProperties.add('comment');
365
382
  }
366
- if (this.diffEnumItems(column1.enumItems, column2.enumItems)) {
367
- log(`'enumItems' changed for column ${tableName}.${column1.name}`, { column1, column2 });
383
+ if (!(fromColumn.mappedType instanceof core_1.ArrayType) &&
384
+ !(toColumn.mappedType instanceof core_1.ArrayType) &&
385
+ this.diffEnumItems(fromColumn.enumItems, toColumn.enumItems)) {
386
+ log(`'enumItems' changed for column ${tableName}.${fromColumn.name}`, { fromColumn, toColumn });
368
387
  changedProperties.add('enumItems');
369
388
  }
370
- if ((column1.extra || '').toLowerCase() !== (column2.extra || '').toLowerCase() &&
371
- !(column1.ignoreSchemaChanges?.includes('extra') ||
372
- column2.ignoreSchemaChanges?.includes('extra'))) {
373
- log(`'extra' changed for column ${tableName}.${column1.name}`, { column1, column2 });
389
+ if ((fromColumn.extra || '').toLowerCase() !== (toColumn.extra || '').toLowerCase() &&
390
+ !(fromColumn.ignoreSchemaChanges?.includes('extra') ||
391
+ toColumn.ignoreSchemaChanges?.includes('extra'))) {
392
+ log(`'extra' changed for column ${tableName}.${fromColumn.name}`, { fromColumn, toColumn });
374
393
  changedProperties.add('extra');
375
394
  }
376
395
  return changedProperties;
@@ -424,11 +443,20 @@ class SchemaComparator {
424
443
  }
425
444
  return index1.primary === index2.primary && index1.unique === index2.unique;
426
445
  }
427
- diffCheck(check1, check2) {
428
- // check constraint definition might be normalized by the driver,
446
+ diffExpression(expr1, expr2) {
447
+ // expressions like check constraints might be normalized by the driver,
429
448
  // e.g. quotes might be added (https://github.com/mikro-orm/mikro-orm/issues/3827)
430
- const simplify = (str) => str?.replace(/['"`()]/g, '').toLowerCase();
431
- return simplify(check1.expression) !== simplify(check2.expression);
449
+ const simplify = (str) => str?.replace(/_\w+\\'(.*?)\\'/g, '$1').replace(/['"`()]|::\w+| +/g, '').toLowerCase();
450
+ return simplify(expr1) !== simplify(expr2);
451
+ }
452
+ parseJsonDefault(defaultValue) {
453
+ if (!defaultValue) {
454
+ return null;
455
+ }
456
+ const val = defaultValue
457
+ .replace(/^(_\w+\\)?'(.*?)\\?'$/, '$2')
458
+ .replace(/^\(?'(.*?)'\)?$/, '$1');
459
+ return (0, core_1.parseJsonSafe)(val);
432
460
  }
433
461
  hasSameDefaultValue(from, to) {
434
462
  if (from.default == null || from.default.toString().toLowerCase() === 'null' || from.default.toString().startsWith('nextval(')) {
@@ -439,6 +467,11 @@ class SchemaComparator {
439
467
  const defaultValueTo = !['0', 'false', 'f', 'n', 'no', 'off'].includes('' + to.default);
440
468
  return defaultValueFrom === defaultValueTo;
441
469
  }
470
+ if (to.mappedType instanceof core_1.JsonType) {
471
+ const defaultValueFrom = this.parseJsonDefault(from.default);
472
+ const defaultValueTo = this.parseJsonDefault(to.default);
473
+ return core_1.Utils.equals(defaultValueFrom, defaultValueTo);
474
+ }
442
475
  if (to.mappedType instanceof core_1.DateTimeType && from.default && to.default) {
443
476
  // normalize now/current_timestamp defaults, also remove `()` from the end of default expression
444
477
  const defaultValueFrom = from.default.toLowerCase().replace('current_timestamp', 'now').replace(/\(\)$/, '');
@@ -1,5 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import type { Connection, Dictionary } from '@mikro-orm/core';
2
+ import { type Connection, type Dictionary } from '@mikro-orm/core';
3
3
  import type { AbstractSqlConnection } from '../AbstractSqlConnection';
4
4
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform';
5
5
  import type { CheckDef, Column, IndexDef, Table, TableDifference } from '../typings';
@@ -18,10 +18,12 @@ export declare abstract class SchemaHelper {
18
18
  getForeignKeys(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<Dictionary>;
19
19
  protected getTableKey(t: Table): string;
20
20
  getEnumDefinitions(connection: AbstractSqlConnection, checks: CheckDef[], tableName: string, schemaName?: string): Promise<Dictionary<string[]>>;
21
- loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[]): Promise<void>;
21
+ getDropNativeEnumSQL(name: string, schema?: string): string;
22
+ getAlterNativeEnumSQL(name: string, schema?: string, value?: string): string;
23
+ loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[]): Promise<void>;
22
24
  getListTablesSQL(schemaName?: string): string;
23
25
  getRenameColumnSQL(tableName: string, oldColumnName: string, to: Column, schemaName?: string): string;
24
- getCreateIndexSQL(tableName: string, index: IndexDef): string;
26
+ getCreateIndexSQL(tableName: string, index: IndexDef, partialExpression?: boolean): string;
25
27
  getDropIndexSQL(tableName: string, index: IndexDef): string;
26
28
  getRenameIndexSQL(tableName: string, index: IndexDef, oldIndexName: string): string;
27
29
  hasNonDefaultPrimaryKeyName(table: DatabaseTable): boolean;
@@ -29,8 +31,8 @@ export declare abstract class SchemaHelper {
29
31
  configureColumn(column: Column, col: Knex.ColumnBuilder, knex: Knex, changedProperties?: Set<string>): Knex.ColumnBuilder;
30
32
  configureColumnDefault(column: Column, col: Knex.ColumnBuilder, knex: Knex, changedProperties?: Set<string>): Knex.ColumnBuilder;
31
33
  getPreAlterTable(tableDiff: TableDifference, safe: boolean): string;
32
- getAlterColumnAutoincrement(tableName: string, column: Column): string;
33
- getChangeColumnCommentSQL(tableName: string, to: Column): string;
34
+ getAlterColumnAutoincrement(tableName: string, column: Column, schemaName?: string): string;
35
+ getChangeColumnCommentSQL(tableName: string, to: Column, schemaName?: string): string;
34
36
  getNamespaces(connection: AbstractSqlConnection): Promise<string[]>;
35
37
  getColumns(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<Column[]>;
36
38
  getIndexes(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<IndexDef[]>;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SchemaHelper = void 0;
4
4
  const core_1 = require("@mikro-orm/core");
5
5
  class SchemaHelper {
6
+ platform;
6
7
  constructor(platform) {
7
8
  this.platform = platform;
8
9
  }
@@ -46,7 +47,13 @@ class SchemaHelper {
46
47
  async getEnumDefinitions(connection, checks, tableName, schemaName) {
47
48
  return {};
48
49
  }
49
- async loadInformationSchema(schema, connection, tables) {
50
+ getDropNativeEnumSQL(name, schema) {
51
+ throw new Error('Not supported by given driver');
52
+ }
53
+ getAlterNativeEnumSQL(name, schema, value) {
54
+ throw new Error('Not supported by given driver');
55
+ }
56
+ async loadInformationSchema(schema, connection, tables, schemas) {
50
57
  for (const t of tables) {
51
58
  const table = schema.addTable(t.table_name, t.schema_name);
52
59
  table.comment = t.table_comment;
@@ -70,14 +77,17 @@ class SchemaHelper {
70
77
  const tableReference = schemaReference + tableName;
71
78
  return `alter table ${tableReference} rename column ${oldColumnName} to ${columnName}`;
72
79
  }
73
- getCreateIndexSQL(tableName, index) {
74
- /* istanbul ignore if */
75
- if (index.expression) {
80
+ getCreateIndexSQL(tableName, index, partialExpression = false) {
81
+ if (index.expression && !partialExpression) {
76
82
  return index.expression;
77
83
  }
78
84
  tableName = this.platform.quoteIdentifier(tableName);
79
85
  const keyName = this.platform.quoteIdentifier(index.keyName);
80
- return `create index ${keyName} on ${tableName} (${index.columnNames.map(c => this.platform.quoteIdentifier(c)).join(', ')})`;
86
+ const sql = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${tableName} `;
87
+ if (index.expression && partialExpression) {
88
+ return `${sql}(${index.expression})`;
89
+ }
90
+ return `${sql}(${index.columnNames.map(c => this.platform.quoteIdentifier(c)).join(', ')})`;
81
91
  }
82
92
  getDropIndexSQL(tableName, index) {
83
93
  return `drop index ${this.platform.quoteIdentifier(index.keyName)}`;
@@ -95,7 +105,7 @@ class SchemaHelper {
95
105
  }
96
106
  createTableColumn(table, column, fromTable, changedProperties) {
97
107
  const compositePK = fromTable.getPrimaryKey()?.composite;
98
- if (column.autoincrement && !compositePK && (!changedProperties || changedProperties.has('autoincrement') || changedProperties.has('type'))) {
108
+ if (column.autoincrement && !column.generated && !compositePK && (!changedProperties || changedProperties.has('autoincrement') || changedProperties.has('type'))) {
99
109
  const primaryKey = !changedProperties && !this.hasNonDefaultPrimaryKeyName(fromTable);
100
110
  if (column.mappedType instanceof core_1.BigIntType) {
101
111
  return table.bigIncrements(column.name, { primaryKey });
@@ -105,12 +115,16 @@ class SchemaHelper {
105
115
  if (column.mappedType instanceof core_1.EnumType && column.enumItems?.every(item => core_1.Utils.isString(item))) {
106
116
  return table.enum(column.name, column.enumItems);
107
117
  }
108
- return table.specificType(column.name, column.type);
118
+ let columnType = column.type;
119
+ if (column.generated) {
120
+ columnType += ` generated always as ${column.generated}`;
121
+ }
122
+ return table.specificType(column.name, columnType);
109
123
  }
110
124
  configureColumn(column, col, knex, changedProperties) {
111
125
  const guard = (key) => !changedProperties || changedProperties.has(key);
112
126
  core_1.Utils.runIfNotEmpty(() => col.nullable(), column.nullable && guard('nullable'));
113
- core_1.Utils.runIfNotEmpty(() => col.notNullable(), !column.nullable);
127
+ core_1.Utils.runIfNotEmpty(() => col.notNullable(), !column.nullable && !column.generated);
114
128
  core_1.Utils.runIfNotEmpty(() => col.unsigned(), column.unsigned);
115
129
  core_1.Utils.runIfNotEmpty(() => col.comment(column.comment), column.comment);
116
130
  this.configureColumnDefault(column, col, knex, changedProperties);
@@ -122,17 +136,17 @@ class SchemaHelper {
122
136
  core_1.Utils.runIfNotEmpty(() => col.defaultTo(column.default == null ? null : knex.raw(column.default)), guard('default'));
123
137
  }
124
138
  else {
125
- core_1.Utils.runIfNotEmpty(() => col.defaultTo(column.default == null ? null : knex.raw(column.default)), column.default !== undefined);
139
+ core_1.Utils.runIfNotEmpty(() => col.defaultTo(knex.raw(column.default)), column.default != null && column.default !== 'null');
126
140
  }
127
141
  return col;
128
142
  }
129
143
  getPreAlterTable(tableDiff, safe) {
130
144
  return '';
131
145
  }
132
- getAlterColumnAutoincrement(tableName, column) {
146
+ getAlterColumnAutoincrement(tableName, column, schemaName) {
133
147
  return '';
134
148
  }
135
- getChangeColumnCommentSQL(tableName, to) {
149
+ getChangeColumnCommentSQL(tableName, to, schemaName) {
136
150
  return '';
137
151
  }
138
152
  async getNamespaces(connection) {
@@ -1,5 +1,4 @@
1
- import type { MikroORM, ISchemaGenerator } from '@mikro-orm/core';
2
- import { AbstractSchemaGenerator } from '@mikro-orm/core';
1
+ import { AbstractSchemaGenerator, type MikroORM, type ISchemaGenerator } from '@mikro-orm/core';
3
2
  import type { SchemaDifference } from '../typings';
4
3
  import { DatabaseSchema } from './DatabaseSchema';
5
4
  import type { AbstractSqlDriver } from '../AbstractSqlDriver';
@@ -8,8 +7,6 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
8
7
  private readonly options;
9
8
  protected lastEnsuredDatabase?: string;
10
9
  static register(orm: MikroORM): void;
11
- /** @deprecated use `dropSchema` and `createSchema` commands respectively */
12
- generate(): Promise<string>;
13
10
  createSchema(options?: {
14
11
  wrap?: boolean;
15
12
  schema?: string;
@@ -82,10 +79,10 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
82
79
  * creates new database and connects to it
83
80
  */
84
81
  createDatabase(name: string): Promise<void>;
85
- dropDatabase(name: string): Promise<void>;
82
+ dropDatabase(name?: string): Promise<void>;
86
83
  execute(sql: string, options?: {
87
84
  wrap?: boolean;
88
- }): Promise<undefined>;
85
+ }): Promise<void>;
89
86
  private wrapSchema;
90
87
  private createSchemaBuilder;
91
88
  private createTable;
@@ -5,22 +5,12 @@ const core_1 = require("@mikro-orm/core");
5
5
  const DatabaseSchema_1 = require("./DatabaseSchema");
6
6
  const SchemaComparator_1 = require("./SchemaComparator");
7
7
  class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
8
- constructor() {
9
- super(...arguments);
10
- this.helper = this.platform.getSchemaHelper();
11
- this.options = this.config.get('schemaGenerator');
12
- }
8
+ helper = this.platform.getSchemaHelper();
9
+ options = this.config.get('schemaGenerator');
10
+ lastEnsuredDatabase;
13
11
  static register(orm) {
14
12
  orm.config.registerExtension('@mikro-orm/schema-generator', () => new SqlSchemaGenerator(orm.em));
15
13
  }
16
- /** @deprecated use `dropSchema` and `createSchema` commands respectively */
17
- async generate() {
18
- const [dropSchema, createSchema] = await Promise.all([
19
- this.getDropSchemaSQL({ wrap: false }),
20
- this.getCreateSchemaSQL({ wrap: false }),
21
- ]);
22
- return this.wrapSchema(dropSchema + createSchema);
23
- }
24
14
  async createSchema(options) {
25
15
  await this.ensureDatabase();
26
16
  const sql = await this.getCreateSchemaSQL(options);
@@ -101,7 +91,8 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
101
91
  await this.ensureDatabase();
102
92
  const wrap = options.wrap ?? this.options.disableForeignKeys;
103
93
  const metadata = this.getOrderedMetadata(options.schema).reverse();
104
- const schema = await DatabaseSchema_1.DatabaseSchema.create(this.connection, this.platform, this.config, options.schema);
94
+ const schemas = this.getTargetSchema(options.schema).getNamespaces();
95
+ const schema = await DatabaseSchema_1.DatabaseSchema.create(this.connection, this.platform, this.config, options.schema, schemas);
105
96
  let ret = '';
106
97
  // remove FKs explicitly if we can't use cascading statement and we don't disable FK checks (we need this for circular relations)
107
98
  for (const meta of metadata) {
@@ -118,6 +109,12 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
118
109
  for (const meta of metadata) {
119
110
  ret += await this.dump(this.dropTable(meta.collection, this.getSchemaName(meta, options)), '\n');
120
111
  }
112
+ if (this.platform.supportsNativeEnums()) {
113
+ for (const columnName of Object.keys(schema.getNativeEnums())) {
114
+ const sql = this.helper.getDropNativeEnumSQL(columnName, options.schema ?? this.config.get('schema'));
115
+ ret += await this.dump(this.knex.schema.raw(sql), '\n');
116
+ }
117
+ }
121
118
  if (options.dropMigrationsTable) {
122
119
  ret += await this.dump(this.dropTable(this.config.get('migrations').tableName, this.config.get('schema')), '\n');
123
120
  }
@@ -140,25 +137,29 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
140
137
  return this.diffToSQL(diffUp, options);
141
138
  }
142
139
  async getUpdateSchemaMigrationSQL(options = {}) {
143
- await this.ensureDatabase();
140
+ if (!options.fromSchema) {
141
+ await this.ensureDatabase();
142
+ }
144
143
  const { fromSchema, toSchema } = await this.prepareSchemaForComparison(options);
145
144
  const comparator = new SchemaComparator_1.SchemaComparator(this.platform);
146
145
  const diffUp = comparator.compare(fromSchema, toSchema);
147
- const diffDown = comparator.compare(toSchema, fromSchema);
146
+ const diffDown = comparator.compare(toSchema, fromSchema, diffUp);
148
147
  return {
149
148
  up: await this.diffToSQL(diffUp, options),
150
149
  down: this.platform.supportsDownMigrations() ? await this.diffToSQL(diffDown, options) : '',
151
150
  };
152
151
  }
153
152
  async prepareSchemaForComparison(options) {
154
- options.wrap ?? (options.wrap = this.options.disableForeignKeys);
155
- options.safe ?? (options.safe = false);
156
- options.dropTables ?? (options.dropTables = true);
153
+ options.wrap ??= this.options.disableForeignKeys;
154
+ options.safe ??= false;
155
+ options.dropTables ??= true;
157
156
  const toSchema = this.getTargetSchema(options.schema);
158
- const fromSchema = options.fromSchema ?? await DatabaseSchema_1.DatabaseSchema.create(this.connection, this.platform, this.config, options.schema);
157
+ const schemas = toSchema.getNamespaces();
158
+ const fromSchema = options.fromSchema ?? await DatabaseSchema_1.DatabaseSchema.create(this.connection, this.platform, this.config, options.schema, schemas);
159
159
  const wildcardSchemaTables = Object.values(this.metadata.getAll()).filter(meta => meta.schema === '*').map(meta => meta.tableName);
160
160
  fromSchema.prune(options.schema, wildcardSchemaTables);
161
161
  toSchema.prune(options.schema, wildcardSchemaTables);
162
+ toSchema.setNativeEnums(fromSchema.getNativeEnums());
162
163
  return { fromSchema, toSchema };
163
164
  }
164
165
  async diffToSQL(schemaDiff, options) {
@@ -214,7 +215,7 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
214
215
  if (schema && schemaName === '*') {
215
216
  return `${schema}.${referencedTableName.replace(/^\*\./, '')}`;
216
217
  }
217
- if (schemaName === this.platform.getDefaultSchemaName()) {
218
+ if (!schemaName || schemaName === this.platform.getDefaultSchemaName()) {
218
219
  return tableName;
219
220
  }
220
221
  return `${schemaName}.${tableName}`;
@@ -262,6 +263,12 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
262
263
  alterTable(diff, safe) {
263
264
  const ret = [];
264
265
  const [schemaName, tableName] = this.splitTableName(diff.name);
266
+ const changedNativeEnums = [];
267
+ for (const { column, changedProperties } of Object.values(diff.changedColumns)) {
268
+ if (column.nativeEnumName && changedProperties.has('enumItems') && column.nativeEnumName in diff.fromTable.nativeEnums) {
269
+ changedNativeEnums.push([column.nativeEnumName, column.enumItems, diff.fromTable.getColumn(column.name).enumItems]);
270
+ }
271
+ }
265
272
  ret.push(this.createSchemaBuilder(schemaName).alterTable(tableName, table => {
266
273
  for (const index of Object.values(diff.removedIndexes)) {
267
274
  this.dropIndex(table, index);
@@ -275,6 +282,14 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
275
282
  for (const check of Object.values(diff.changedChecks)) {
276
283
  this.dropCheck(table, check);
277
284
  }
285
+ /* istanbul ignore else */
286
+ if (!safe) {
287
+ for (const column of Object.values(diff.removedColumns)) {
288
+ this.helper.pushTableQuery(table, `alter table ${this.platform.quoteIdentifier(tableName)} drop column ${this.platform.quoteIdentifier(column.name)}`);
289
+ }
290
+ }
291
+ }));
292
+ ret.push(this.createSchemaBuilder(schemaName).alterTable(tableName, table => {
278
293
  for (const column of Object.values(diff.addedColumns)) {
279
294
  const col = this.helper.createTableColumn(table, column, diff.fromTable);
280
295
  this.helper.configureColumn(column, col, this.knex);
@@ -288,27 +303,24 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
288
303
  .onDelete(foreignKey.deleteRule);
289
304
  }
290
305
  }
291
- /* istanbul ignore else */
292
- if (!safe) {
293
- for (const column of Object.values(diff.removedColumns)) {
294
- table.dropColumn(column.name);
295
- }
296
- }
297
306
  for (const { column, changedProperties } of Object.values(diff.changedColumns)) {
298
307
  if (changedProperties.size === 1 && changedProperties.has('comment')) {
299
308
  continue;
300
309
  }
310
+ if (changedProperties.size === 1 && changedProperties.has('enumItems') && column.nativeEnumName) {
311
+ continue;
312
+ }
301
313
  const col = this.helper.createTableColumn(table, column, diff.fromTable, changedProperties).alter();
302
314
  this.helper.configureColumn(column, col, this.knex, changedProperties);
303
315
  }
304
316
  for (const { column } of Object.values(diff.changedColumns).filter(diff => diff.changedProperties.has('autoincrement'))) {
305
- this.helper.pushTableQuery(table, this.helper.getAlterColumnAutoincrement(tableName, column));
317
+ this.helper.pushTableQuery(table, this.helper.getAlterColumnAutoincrement(tableName, column, schemaName));
306
318
  }
307
319
  for (const { column, changedProperties } of Object.values(diff.changedColumns).filter(diff => diff.changedProperties.has('comment'))) {
308
320
  if (['type', 'nullable', 'autoincrement', 'unsigned', 'default', 'enumItems'].some(t => changedProperties.has(t))) {
309
321
  continue; // will be handled via knex
310
322
  }
311
- this.helper.pushTableQuery(table, this.helper.getChangeColumnCommentSQL(tableName, column));
323
+ this.helper.pushTableQuery(table, this.helper.getChangeColumnCommentSQL(tableName, column, schemaName));
312
324
  }
313
325
  for (const [oldColumnName, column] of Object.entries(diff.renamedColumns)) {
314
326
  this.helper.pushTableQuery(table, this.helper.getRenameColumnSQL(tableName, oldColumnName, column, schemaName));
@@ -345,6 +357,13 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
345
357
  table.comment(comment);
346
358
  }
347
359
  }));
360
+ if (this.platform.supportsNativeEnums()) {
361
+ changedNativeEnums.forEach(([enumName, itemsNew, itemsOld]) => {
362
+ // postgres allows only adding new items
363
+ const newItems = itemsNew.filter(val => !itemsOld.includes(val));
364
+ ret.push(...newItems.map(val => this.knex.schema.raw(this.helper.getAlterNativeEnumSQL(enumName, schemaName, val))));
365
+ });
366
+ }
348
367
  return ret;
349
368
  }
350
369
  /**
@@ -356,24 +375,26 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
356
375
  await this.driver.reconnect();
357
376
  }
358
377
  async dropDatabase(name) {
378
+ name ??= this.config.get('dbName');
359
379
  this.config.set('dbName', this.helper.getManagementDbName());
360
380
  await this.driver.reconnect();
361
381
  await this.driver.execute(this.helper.getDropDatabaseSQL('' + this.knex.ref(name)));
362
382
  }
363
383
  async execute(sql, options = {}) {
364
- options.wrap ?? (options.wrap = false);
384
+ options.wrap ??= false;
365
385
  const lines = this.wrapSchema(sql, options).split('\n').filter(i => i.trim());
366
386
  if (lines.length === 0) {
367
387
  return;
368
388
  }
369
389
  if (this.platform.supportsMultipleStatements()) {
370
390
  const query = lines.join('\n');
371
- return void await this.driver.execute(query);
391
+ await this.driver.execute(query);
392
+ return;
372
393
  }
373
394
  await core_1.Utils.runSerial(lines, line => this.driver.execute(line));
374
395
  }
375
- wrapSchema(sql, options = {}) {
376
- options.wrap ?? (options.wrap = this.options.disableForeignKeys);
396
+ wrapSchema(sql, options) {
397
+ options.wrap ??= this.options.disableForeignKeys;
377
398
  if (!options.wrap || sql.trim() === '') {
378
399
  return sql;
379
400
  }
@@ -423,7 +444,14 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
423
444
  table.primary(index.columnNames, keyName);
424
445
  }
425
446
  else if (index.unique) {
426
- table.unique(index.columnNames, { indexName: index.keyName });
447
+ // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
448
+ if (index.columnNames.some(column => column.includes('.'))) {
449
+ const columns = this.platform.getJsonIndexDefinition(index);
450
+ table.index(columns.map(column => this.knex.raw(`(${column})`)), index.keyName, { indexType: 'unique' });
451
+ }
452
+ else {
453
+ table.unique(index.columnNames, { indexName: index.keyName });
454
+ }
427
455
  }
428
456
  else if (index.expression) {
429
457
  this.helper.pushTableQuery(table, index.expression);
@@ -435,14 +463,21 @@ class SqlSchemaGenerator extends core_1.AbstractSchemaGenerator {
435
463
  }
436
464
  }
437
465
  else {
438
- table.index(index.columnNames, index.keyName, index.type);
466
+ // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
467
+ if (index.columnNames.some(column => column.includes('.'))) {
468
+ const columns = this.platform.getJsonIndexDefinition(index);
469
+ table.index(columns.map(column => this.knex.raw(`(${column})`)), index.keyName, index.type);
470
+ }
471
+ else {
472
+ table.index(index.columnNames, index.keyName, index.type);
473
+ }
439
474
  }
440
475
  }
441
476
  dropIndex(table, index, oldIndexName = index.keyName) {
442
477
  if (index.primary) {
443
478
  table.dropPrimary(oldIndexName);
444
479
  }
445
- else if (index.unique) {
480
+ else if (index.unique && index.constraint) {
446
481
  table.dropUnique(index.columnNames, oldIndexName);
447
482
  }
448
483
  else {