Skip to content

Commit

Permalink
fix: migrations run in reverse order for mongodb (#4702)
Browse files Browse the repository at this point in the history
* update `loadExecutedMigrations` to get migrations sorted by id from database
* add missing await statements when running mongodb migrations
  • Loading branch information
RecuencoJones authored and pleerock committed Oct 18, 2019
1 parent 4a62b1c commit 2f27581
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 8 deletions.
22 changes: 14 additions & 8 deletions src/migration/MigrationExecutor.ts
Expand Up @@ -291,16 +291,22 @@ export class MigrationExecutor {
}

/**
* Loads all migrations that were executed and saved into the database.
* Loads all migrations that were executed and saved into the database (sorts by id).
*/
protected async loadExecutedMigrations(queryRunner: QueryRunner): Promise<Migration[]> {
if (this.connection.driver instanceof MongoDriver) {
const mongoRunner = queryRunner as MongoQueryRunner;
return await mongoRunner.databaseConnection.db(this.connection.driver.database!).collection(this.migrationsTableName).find().toArray();
return await mongoRunner.databaseConnection
.db(this.connection.driver.database!)
.collection(this.migrationsTableName)
.find<Migration>()
.sort("_id", -1)
.toArray();
} else {
const migrationsRaw: ObjectLiteral[] = await this.connection.manager
.createQueryBuilder(queryRunner)
.select()
.orderBy("id", "DESC")
.from(this.migrationsTable, this.migrationsTableName)
.getRawMany();
return migrationsRaw.map(migrationRaw => {
Expand Down Expand Up @@ -335,10 +341,10 @@ export class MigrationExecutor {
}

/**
* Finds the latest migration (sorts by id) in the given array of migrations.
* Finds the latest migration in the given array of migrations.
* PRE: Migration array must be sorted by descending id.
*/
protected getLatestExecutedMigration(migrations: Migration[]): Migration|undefined {
const sortedMigrations = migrations.map(migration => migration).sort((a, b) => ((a.id || 0) - (b.id || 0)) * -1);
protected getLatestExecutedMigration(sortedMigrations: Migration[]): Migration|undefined {
return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined;
}

Expand All @@ -354,9 +360,9 @@ export class MigrationExecutor {
values["timestamp"] = migration.timestamp;
values["name"] = migration.name;
}
if (this.connection.driver instanceof MongoDriver) {
if (this.connection.driver instanceof MongoDriver) {
const mongoRunner = queryRunner as MongoQueryRunner;
mongoRunner.databaseConnection.db(this.connection.driver.database!).collection(this.migrationsTableName).insert(values);
await mongoRunner.databaseConnection.db(this.connection.driver.database!).collection(this.migrationsTableName).insert(values);
} else {
const qb = queryRunner.manager.createQueryBuilder();
await qb.insert()
Expand All @@ -382,7 +388,7 @@ export class MigrationExecutor {

if (this.connection.driver instanceof MongoDriver) {
const mongoRunner = queryRunner as MongoQueryRunner;
mongoRunner.databaseConnection.db(this.connection.driver.database!).collection(this.migrationsTableName).deleteOne(conditions);
await mongoRunner.databaseConnection.db(this.connection.driver.database!).collection(this.migrationsTableName).deleteOne(conditions);
} else {
const qb = queryRunner.manager.createQueryBuilder();
await qb.delete()
Expand Down
16 changes: 16 additions & 0 deletions test/github-issues/4697/entity/config.entity.ts
@@ -0,0 +1,16 @@
import {Entity, ObjectIdColumn, ObjectID, Column} from "../../../../src";

/**
* @deprecated use item config instead
*/
@Entity()
export class Config {
@ObjectIdColumn()
_id: ObjectID;

@Column()
itemId: string;

@Column({ type: "json" })
data: any;
}
19 changes: 19 additions & 0 deletions test/github-issues/4697/entity/item.entity.ts
@@ -0,0 +1,19 @@
import {Entity, ObjectIdColumn, ObjectID, Column} from "../../../../src";

@Entity()
export class Item {
@ObjectIdColumn()
public _id: ObjectID;

/**
* @deprecated use contacts instead
*/
@Column()
public contact?: string;

@Column({ array: true })
public contacts: Array<string>;

@Column({ type: "json" })
config: any;
}
28 changes: 28 additions & 0 deletions test/github-issues/4697/issue-4697.ts
@@ -0,0 +1,28 @@
import "reflect-metadata";
import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";

describe("github issues > #4697 Revert migrations running in reverse order.", () => {

let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
migrations: [__dirname + "/migration/*.js"],
enabledDrivers: ["mongodb"],
schemaCreate: true,
dropSchema: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));

it("should revert migrations in the right order", () => Promise.all(connections.map(async connection => {
await connection.runMigrations();

await connection.undoLastMigration();

const [lastMigration] = await connection.runMigrations();

lastMigration.should.have.property("timestamp", 1567689639607);
lastMigration.should.have.property("name", "MergeConfigs1567689639607");
})));
});
30 changes: 30 additions & 0 deletions test/github-issues/4697/migration/1566560354098-UpdateContacts.ts
@@ -0,0 +1,30 @@
import {MigrationInterface, QueryRunner} from "../../../../src";
import {Item} from "../entity/item.entity";

export class UpdateContacts1566560354098 implements MigrationInterface {

public async up({connection}: QueryRunner): Promise<any> {
const repo = connection.getMongoRepository(Item);
const items: Array<Item> = await repo.find();

items.forEach((item) => {
if (!item.contacts) {
item.contacts = [item.contact || ""];
}
});

await repo.save(items);
}

public async down({connection}: QueryRunner): Promise<any> {
const repo = connection.getMongoRepository(Item);
const items: Array<Item> = await repo.find();

items.forEach((item) => {
item.contact = item.contacts[0];
});

await repo.save(items);
}

}
44 changes: 44 additions & 0 deletions test/github-issues/4697/migration/1567689639607-MergeConfigs.ts
@@ -0,0 +1,44 @@
import {MigrationInterface, QueryRunner} from "../../../../src";
import {Item} from "../entity/item.entity";
import {Config} from "../entity/config.entity";

export class MergeConfigs1567689639607 implements MigrationInterface {

public async up({connection}: QueryRunner): Promise<any> {
const itemRepository = connection.getMongoRepository(Item);
const configRepository = connection.getMongoRepository(Config);

const configs = await configRepository.find();

await Promise.all(configs.map(async ({itemId, data}) => {
const item = await itemRepository.findOne(itemId);

if (item) {
item.config = data;

return itemRepository.save(item);
} else {
console.warn(`No item found with id: ${ itemId }. Ignoring.`);

return null;
}
}));
}

public async down({connection}: QueryRunner): Promise<any> {
const itemRepository = connection.getRepository(Item);
const configRepository = connection.getRepository(Config);

const items = await itemRepository.find();

await Promise.all(items.map((item) => {
const config = new Config();

config.itemId = item._id.toString();
config.data = item.config;

return configRepository.save(config);
}));
}

}

0 comments on commit 2f27581

Please sign in to comment.