@mikro-orm/migrations 7.2.7-dev.8 → 7.2.7

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
@@ -27,6 +27,7 @@ export declare class Migrator extends AbstractMigrator<AbstractSqlDriver> {
27
27
  */
28
28
  createInitial(path?: string, name?: string, blank?: boolean): Promise<MigrationResult>;
29
29
  protected runMigrations(method: 'up' | 'down', options?: string | string[] | MigrateOptions): Promise<MigrationInfo[]>;
30
+ private runWithoutForeignKeys;
30
31
  /** Whether the introspected schema differs from the snapshot in any way the comparator turns into SQL. */
31
32
  private snapshotDiffers;
32
33
  getStorage(): MigrationStorage;
package/Migrator.js CHANGED
@@ -148,9 +148,9 @@ export class Migrator extends AbstractMigrator {
148
148
  };
149
149
  }
150
150
  async runMigrations(method, options) {
151
- const result = await super.runMigrations(method, options);
151
+ const ctx = Utils.isObject(options) ? options.transaction : undefined;
152
+ const result = await this.runWithoutForeignKeys(ctx, () => super.runMigrations(method, options));
152
153
  if (result.length > 0 && this.options.snapshot && this.options.snapshotOnMigrate) {
153
- const ctx = Utils.isObject(options) ? options.transaction : undefined;
154
154
  const { skipTables, skipViews } = this.config.get('schemaGenerator');
155
155
  const schema = await DatabaseSchema.create(this.em.getConnection(), this.em.getPlatform(), this.config, undefined, undefined, undefined, skipTables, skipViews, ctx);
156
156
  // keep the snapshot authored by `migration:create` when the migrated DB still matches it
@@ -169,6 +169,14 @@ export class Migrator extends AbstractMigrator {
169
169
  }
170
170
  return result;
171
171
  }
172
+ async runWithoutForeignKeys(ctx, run) {
173
+ // sqlite ignores `pragma foreign_keys` inside a transaction, so a table rebuild would run the FK actions of child rows
174
+ if (ctx || !this.options.transactional || !this.driver.getPlatform().getSchemaHelper().getForeignKeyCheckSQL()) {
175
+ return run();
176
+ }
177
+ await this.init(); // can recreate the runner, which holds the FK state
178
+ return this.runner.runWithoutForeignKeys(run);
179
+ }
172
180
  /** Whether the introspected schema differs from the snapshot in any way the comparator turns into SQL. */
173
181
  snapshotDiffers(snapshot, schema) {
174
182
  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.2.7-dev.8",
3
+ "version": "7.2.7",
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.2.7-dev.8"
50
+ "@mikro-orm/sql": "7.2.7"
51
51
  },
52
52
  "devDependencies": {
53
- "@mikro-orm/core": "^7.2.6"
53
+ "@mikro-orm/core": "^7.2.7"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.2.7-dev.8"
56
+ "@mikro-orm/core": "7.2.7"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"