@mikro-orm/migrations 7.3.0-dev.16 → 7.3.0-dev.18

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.
@@ -9,6 +9,11 @@ export declare class MigrationRunner {
9
9
  protected readonly config: Configuration;
10
10
  constructor(driver: AbstractSqlDriver, options: MigrationsOptions, config: Configuration);
11
11
  run(migration: Migration, method: 'up' | 'down', afterRun?: (tx?: Transaction) => Promise<void>): Promise<void>;
12
+ /** Runs migrations with FK checks off, for drivers that ignore the switch inside a transaction (SQLite). */
13
+ runWithoutForeignKeys<T>(run: () => Promise<T>): Promise<T>;
14
+ private runWithForeignKeys;
15
+ private setForeignKeys;
16
+ private countForeignKeyViolations;
12
17
  private resetSessionSchema;
13
18
  setMasterMigration(trx: Transaction): void;
14
19
  unsetMasterMigration(): void;
@@ -1,4 +1,4 @@
1
- import { Utils } from '@mikro-orm/core';
1
+ import { Utils, ValidationError } from '@mikro-orm/core';
2
2
  /** Executes individual migration files within optional transaction contexts. */
3
3
  export class MigrationRunner {
4
4
  driver;
@@ -8,6 +8,7 @@ export class MigrationRunner {
8
8
  #helper;
9
9
  #masterTransaction;
10
10
  #runSchema;
11
+ #foreignKeysOff = false;
11
12
  constructor(driver, options, config) {
12
13
  this.driver = driver;
13
14
  this.options = options;
@@ -23,16 +24,23 @@ export class MigrationRunner {
23
24
  if (this.#runSchema) {
24
25
  throw new Error('Runtime schema (migrations.schema / migrator.up({ schema })) is only supported with transactional migrations');
25
26
  }
26
- const queries = await this.getQueries(migration, method);
27
- await Utils.runSerial(queries, sql => this.driver.execute(sql));
28
- await afterRun?.();
27
+ await this.runWithForeignKeys(async () => {
28
+ const queries = await this.getQueries(migration, method);
29
+ await Utils.runSerial(queries, sql => this.driver.execute(sql));
30
+ await afterRun?.();
31
+ });
29
32
  }
30
33
  else {
31
34
  await this.#connection.transactional(async (tx) => {
32
35
  migration.setTransactionContext(tx);
36
+ const violations = await this.countForeignKeyViolations(tx);
33
37
  try {
34
38
  const queries = await this.getQueries(migration, method);
35
39
  await Utils.runSerial(queries, sql => this.driver.execute(sql, undefined, 'all', tx));
40
+ const added = (await this.countForeignKeyViolations(tx)) - violations;
41
+ if (added > 0) {
42
+ throw new ValidationError(`Migration ${migration.name ?? migration.constructor.name} left ${added} rows that violate foreign keys`);
43
+ }
36
44
  await afterRun?.(tx);
37
45
  }
38
46
  finally {
@@ -41,6 +49,41 @@ export class MigrationRunner {
41
49
  }, { ctx: this.#masterTransaction });
42
50
  }
43
51
  }
52
+ /** Runs migrations with FK checks off, for drivers that ignore the switch inside a transaction (SQLite). */
53
+ async runWithoutForeignKeys(run) {
54
+ await this.setForeignKeys(false);
55
+ try {
56
+ return await run();
57
+ }
58
+ finally {
59
+ await this.setForeignKeys(true);
60
+ }
61
+ }
62
+ // a migration outside a transaction gets no `foreign_key_check`, so it runs with FK checks on
63
+ async runWithForeignKeys(run) {
64
+ if (!this.#foreignKeysOff) {
65
+ return run();
66
+ }
67
+ await this.setForeignKeys(true);
68
+ try {
69
+ await run();
70
+ }
71
+ finally {
72
+ await this.setForeignKeys(false);
73
+ }
74
+ }
75
+ async setForeignKeys(enabled) {
76
+ await this.driver.execute(enabled ? this.#helper.enableForeignKeysSQL() : this.#helper.disableForeignKeysSQL());
77
+ this.#foreignKeysOff = !enabled;
78
+ }
79
+ async countForeignKeyViolations(ctx) {
80
+ const sql = this.#helper.getForeignKeyCheckSQL();
81
+ // with FK checks on (caller-supplied transaction), the database enforces them itself
82
+ if (!this.#foreignKeysOff || !sql) {
83
+ return 0;
84
+ }
85
+ return (await this.driver.execute(sql, undefined, 'all', ctx)).length;
86
+ }
44
87
  async resetSessionSchema(ctx) {
45
88
  if (!this.#runSchema) {
46
89
  return;
package/Migrator.d.ts CHANGED
@@ -29,6 +29,7 @@ export declare class Migrator extends AbstractMigrator<AbstractSqlDriver> {
29
29
  */
30
30
  createInitial(path?: string, name?: string, blank?: boolean): Promise<MigrationResult>;
31
31
  protected runMigrations(method: 'up' | 'down', options?: string | string[] | MigrateOptions): Promise<MigrationInfo[]>;
32
+ private runWithoutForeignKeys;
32
33
  /** Whether the introspected schema differs from the snapshot in any way the comparator turns into SQL. */
33
34
  private snapshotDiffers;
34
35
  getStorage(): MigrationStorage;
package/Migrator.js CHANGED
@@ -188,9 +188,9 @@ export class Migrator extends AbstractMigrator {
188
188
  };
189
189
  }
190
190
  async runMigrations(method, options) {
191
- const result = await this.runLocked(() => super.runMigrations(method, options));
191
+ const ctx = Utils.isObject(options) ? options.transaction : undefined;
192
+ const result = await this.runLocked(() => this.runWithoutForeignKeys(ctx, () => super.runMigrations(method, options)));
192
193
  if (result.length > 0 && this.options.snapshot && this.options.snapshotOnMigrate) {
193
- const ctx = Utils.isObject(options) ? options.transaction : undefined;
194
194
  const { skipTables, skipViews } = this.config.get('schemaGenerator');
195
195
  const schema = await DatabaseSchema.create(this.em.getConnection(), this.em.getPlatform(), this.config, undefined, undefined, undefined, skipTables, skipViews, ctx);
196
196
  // keep the snapshot authored by `migration:create` when the migrated DB still matches it
@@ -209,6 +209,14 @@ export class Migrator extends AbstractMigrator {
209
209
  }
210
210
  return result;
211
211
  }
212
+ async runWithoutForeignKeys(ctx, run) {
213
+ // sqlite ignores `pragma foreign_keys` inside a transaction, so a table rebuild would run the FK actions of child rows
214
+ if (ctx || !this.options.transactional || !this.driver.getPlatform().getSchemaHelper().getForeignKeyCheckSQL()) {
215
+ return run();
216
+ }
217
+ await this.init(); // can recreate the runner, which holds the FK state
218
+ return this.runner.runWithoutForeignKeys(run);
219
+ }
212
220
  /** Whether the introspected schema differs from the snapshot in any way the comparator turns into SQL. */
213
221
  snapshotDiffers(snapshot, schema) {
214
222
  const comparator = new SchemaComparator(this.driver.getPlatform());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/migrations",
3
- "version": "7.3.0-dev.16",
3
+ "version": "7.3.0-dev.18",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "keywords": [
6
6
  "data-mapper",
@@ -47,13 +47,13 @@
47
47
  "copy": "node ../../scripts/copy.mjs"
48
48
  },
49
49
  "dependencies": {
50
- "@mikro-orm/sql": "7.3.0-dev.16"
50
+ "@mikro-orm/sql": "7.3.0-dev.18"
51
51
  },
52
52
  "devDependencies": {
53
- "@mikro-orm/core": "^7.2.3"
53
+ "@mikro-orm/core": "^7.2.6"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.3.0-dev.16"
56
+ "@mikro-orm/core": "7.3.0-dev.18"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"