Skip to content

Commit

Permalink
fix(migrator): store snapshot only after migration is generated (#5470)
Browse files Browse the repository at this point in the history
### The problem
When using custom migration fileName function which forces using name
(throws) as in the example in documentaion
https://mikro-orm.io/docs/migrations#using-custom-migration-names and
having snapshots enabled, causes the update of snapshot. Second retry
with using name is not generating migration, because the snapshot has
already been updated and no changes are found.

 ```typescript
 migrations: {
  fileName: (timestamp: string, name?: string) => {
// force user to provide the name, otherwise you would end up with
`Migration20230421212713_undefined`
    if (!name) {
throw new Error('Specify migration name via `mikro-orm migration:create
--name=...`');
    }

    return `Migration${timestamp}_${name}`;
  },
  snapshot: true,
},
 ```

### Description of the solution
Delaying update of snapshot after migration file is actually generated.
Thus, updating it only after successful call to
`this.generator.generate` which, under the hood, calls custom `fileName`
function which is throwing error.
  • Loading branch information
Ruby184 committed Apr 18, 2024
1 parent b877e40 commit 65ec57c
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 2 deletions.
5 changes: 3 additions & 2 deletions packages/migrations/src/Migrator.ts
Expand Up @@ -80,8 +80,9 @@ export class Migrator implements IMigrator {
return { fileName: '', code: '', diff };
}

await this.storeCurrentSchema();
const migration = await this.generator.generate(diff, path, name);
await this.storeCurrentSchema();

return {
fileName: migration[1],
code: migration[0],
Expand All @@ -102,8 +103,8 @@ export class Migrator implements IMigrator {
await this.ensureMigrationsDirExists();
const schemaExists = await this.validateInitialMigration();
const diff = await this.getSchemaDiff(false, true);
await this.storeCurrentSchema();
const migration = await this.generator.generate(diff, path, name);
await this.storeCurrentSchema();

if (schemaExists) {
await this.storage.logMigration({ name: migration[1], context: null });
Expand Down
32 changes: 32 additions & 0 deletions tests/features/migrations/Migrator.test.ts
Expand Up @@ -107,6 +107,38 @@ describe('Migrator', () => {
downMock.mockRestore();
});

test('snapshot should not be updated when custom migration fileName function throws', async () => {
const dateMock = jest.spyOn(Date.prototype, 'toISOString');
dateMock.mockReturnValue('2019-10-13T21:48:13.382Z');
const migrationsSettings = orm.config.get('migrations');
orm.config.set('migrations', {
...migrationsSettings,
fileName: (timestamp, name?) => {
if (!name) {
throw new Error('Migration name is required');
}

return `migration-${timestamp}-${name}`;
},
snapshot: true,
});
const migrator = orm.migrator;
await expect(migrator.createMigration()).rejects.toThrow(
'Migration name is required',
);
// retry creating migration with specified name
const migration = await migrator.createMigration(
undefined,
false,
false,
'with-custom-name',
);
expect(migration.fileName).toEqual('migration-20191013214813-with-custom-name.ts');
expect(migration).toMatchSnapshot('migration-dump');
orm.config.set('migrations', migrationsSettings); // Revert migration config changes
await remove(process.cwd() + '/temp/migrations-123/' + migration.fileName);
});

test('generate schema migration', async () => {
const dateMock = jest.spyOn(Date.prototype, 'toISOString');
dateMock.mockReturnValue('2019-10-13T21:48:13.382Z');
Expand Down
46 changes: 46 additions & 0 deletions tests/features/migrations/__snapshots__/Migrator.test.ts.snap
Expand Up @@ -822,6 +822,52 @@ export class Migration20191013214813 extends Migration {
}
`;

exports[`Migrator snapshot should not be updated when custom migration fileName function throws: migration-dump 1`] = `
{
"code": "import { Migration } from '@mikro-orm/migrations';
export class Migration20191013214813_with-custom-name extends Migration {
async up(): Promise<void> {
this.addSql('alter table \`test2\` drop foreign key \`test2_foo___bar_foreign\`;');
this.addSql('alter table \`book2\` drop column \`foo\`;');
this.addSql('alter table \`test2\` drop index \`test2_foo___bar_unique\`;');
this.addSql('alter table \`test2\` drop column \`foo___bar\`, drop column \`foo___baz\`;');
}
async down(): Promise<void> {
this.addSql('alter table \`book2\` add \`foo\` varchar(255) null default \\'lol\\';');
this.addSql('alter table \`test2\` add \`foo___bar\` int unsigned null, add \`foo___baz\` int unsigned null;');
this.addSql('alter table \`test2\` add constraint \`test2_foo___bar_foreign\` foreign key (\`foo___bar\`) references \`foo_bar2\` (\`id\`) on update cascade on delete set null;');
this.addSql('alter table \`test2\` add unique \`test2_foo___bar_unique\`(\`foo___bar\`);');
}
}
",
"diff": {
"down": [
"alter table \`book2\` add \`foo\` varchar(255) null default 'lol';",
"",
"alter table \`test2\` add \`foo___bar\` int unsigned null, add \`foo___baz\` int unsigned null;",
"alter table \`test2\` add constraint \`test2_foo___bar_foreign\` foreign key (\`foo___bar\`) references \`foo_bar2\` (\`id\`) on update cascade on delete set null;",
"alter table \`test2\` add unique \`test2_foo___bar_unique\`(\`foo___bar\`);",
],
"up": [
"alter table \`test2\` drop foreign key \`test2_foo___bar_foreign\`;",
"",
"alter table \`book2\` drop column \`foo\`;",
"",
"alter table \`test2\` drop index \`test2_foo___bar_unique\`;",
"alter table \`test2\` drop column \`foo___bar\`, drop column \`foo___baz\`;",
],
},
"fileName": "migration-20191013214813-with-custom-name.ts",
}
`;

exports[`Migrator up/down params [all or nothing disabled]: all-or-nothing-disabled 1`] = `
[
"select 1 from information_schema.schemata where schema_name = 'mikro_orm_test_migrations' (via write connection '127.0.0.1')",
Expand Down

0 comments on commit 65ec57c

Please sign in to comment.