Skip to content

Commit

Permalink
fix: create typeorm_metadata table when running migrations (#4956)
Browse files Browse the repository at this point in the history
* fix: create typeorm_metatable when running migrations

* remove redundant check

* create typeorm_metadata table in MigrationExecutor

* remove redundant call to createMetadataTableIfNecessary

* move create metadata table logic

* add missing test (#4956)

* fix migration name

* pass query runner instance to metadata creation

* do not create metadata table in log()
Avoid creating the metatable when you don't intend to modify the database, such as when generating migrations
  • Loading branch information
BitPatty committed Nov 14, 2021
1 parent 91080be commit b2c8168
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 15 deletions.
11 changes: 10 additions & 1 deletion src/migration/MigrationExecutor.ts
Expand Up @@ -5,9 +5,10 @@ import {ObjectLiteral} from "../common/ObjectLiteral";
import {QueryRunner} from "../query-runner/QueryRunner";
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
import {MssqlParameter} from "../driver/sqlserver/MssqlParameter";
import {RdbmsSchemaBuilder} from "../schema-builder/RdbmsSchemaBuilder";
import {MongoDriver} from "../driver/mongodb/MongoDriver";
import {MongoQueryRunner} from "../driver/mongodb/MongoQueryRunner";
import { TypeORMError } from "../error";
import {TypeORMError} from "../error";

/**
* Executes migrations: runs pending and reverts previously executed migrations.
Expand Down Expand Up @@ -157,6 +158,14 @@ export class MigrationExecutor {
const queryRunner = this.queryRunner || this.connection.createQueryRunner();
// create migrations table if its not created yet
await this.createMigrationsTableIfNotExist(queryRunner);

// create the typeorm_metadata table if necessary
const schemaBuilder = this.connection.driver.createSchemaBuilder();

if (schemaBuilder instanceof RdbmsSchemaBuilder) {
await schemaBuilder.createMetadataTableIfNecessary(queryRunner);
}

// get all migrations that are executed and saved in the database
const executedMigrations = await this.loadExecutedMigrations(queryRunner);

Expand Down
27 changes: 13 additions & 14 deletions src/schema-builder/RdbmsSchemaBuilder.ts
Expand Up @@ -17,7 +17,7 @@ import {TableUnique} from "./table/TableUnique";
import {TableCheck} from "./table/TableCheck";
import {TableExclusion} from "./table/TableExclusion";
import {View} from "./view/View";
import { ViewUtils } from "./util/ViewUtils";
import {ViewUtils} from "./util/ViewUtils";
import {AuroraDataApiDriver} from "../driver/aurora-data-api/AuroraDataApiDriver";

/**
Expand Down Expand Up @@ -77,10 +77,7 @@ export class RdbmsSchemaBuilder implements SchemaBuilder {
}

try {
if (this.viewEntityToSyncMetadatas.length > 0 || (this.connection.driver instanceof PostgresDriver && this.connection.driver.isGeneratedColumnsSupported)) {
await this.createTypeormMetadataTable();
}

await this.createMetadataTableIfNecessary(this.queryRunner);
// Flush the queryrunner table & view cache
const tablePaths = this.entityToSyncMetadatas.map(metadata => this.getTablePath(metadata));

Expand Down Expand Up @@ -111,19 +108,21 @@ export class RdbmsSchemaBuilder implements SchemaBuilder {
}
}

/**
* If the schema contains views, create the typeorm_metadata table if it doesn't exist yet
*/
async createMetadataTableIfNecessary(queryRunner: QueryRunner): Promise<void> {
if (this.viewEntityToSyncMetadatas.length > 0 || (this.connection.driver instanceof PostgresDriver && this.connection.driver.isGeneratedColumnsSupported)) {
await this.createTypeormMetadataTable(queryRunner);
}
}

/**
* Returns sql queries to be executed by schema builder.
*/
async log(): Promise<SqlInMemory> {
this.queryRunner = this.connection.createQueryRunner();
try {

// TODO: typeorm_metadata table needs only for Views for now.
// Remove condition or add new conditions if necessary (for CHECK constraints for example).
if (this.viewEntityToSyncMetadatas.length > 0) {
await this.createTypeormMetadataTable();
}

// Flush the queryrunner table & view cache
const tablePaths = this.entityToSyncMetadatas.map(metadata => this.getTablePath(metadata));
await this.queryRunner.getTables(tablePaths);
Expand Down Expand Up @@ -831,12 +830,12 @@ export class RdbmsSchemaBuilder implements SchemaBuilder {
/**
* Creates typeorm service table for storing user defined Views.
*/
protected async createTypeormMetadataTable() {
protected async createTypeormMetadataTable(queryRunner: QueryRunner) {
const schema = this.currentSchema;
const database = this.currentDatabase;
const typeormMetadataTable = this.connection.driver.buildTableName("typeorm_metadata", schema, database);

await this.queryRunner.createTable(new Table(
await queryRunner.createTable(new Table(
{
database: database,
schema: schema,
Expand Down
12 changes: 12 additions & 0 deletions test/github-issues/4956/entities/Foo.ts
@@ -0,0 +1,12 @@
import { PrimaryGeneratedColumn, UpdateDateColumn } from "../../../../src";

import { Entity } from "../../../../src/decorator/entity/Entity";

@Entity("foo")
export class Foo {
@PrimaryGeneratedColumn({ name: "id" })
id: number;

@UpdateDateColumn({ name: "updated_at" })
updatedAt: Date;
}
13 changes: 13 additions & 0 deletions test/github-issues/4956/entities/FooView.ts
@@ -0,0 +1,13 @@
import { Connection, ViewColumn, ViewEntity } from "../../../../src";

import { Foo } from "./Foo";

@ViewEntity({
name: "foo_view",
expression: (connection: Connection) =>
connection.createQueryBuilder(Foo, "foo").select(`foo.updatedAt`),
})
export class FooView {
@ViewColumn()
updatedAt: Date;
}
78 changes: 78 additions & 0 deletions test/github-issues/4956/issue-4956.ts
@@ -0,0 +1,78 @@
import "reflect-metadata";

import {
closeTestingConnections,
createTestingConnections,
} from "../../utils/test-utils";

import { Connection } from "../../../src/connection/Connection";
import { afterEach } from "mocha";
import { expect } from "chai";

describe("github issues > #4956 create typeorm_metatable when running migrations.", () => {
let connections: Connection[];

afterEach(async () => {
await closeTestingConnections(connections);
});

it("should create typeorm_metadata table when running migrations with views", async () => {
connections = await createTestingConnections({
entities: [__dirname + "/entities/*{.js,.ts}"],
migrations: [__dirname + "/migrations/WithView{.js,.ts}"],
enabledDrivers: ["mysql", "mariadb"],
schemaCreate: false,
dropSchema: true,
});

await Promise.all(
connections.map(async (connection) => {
const queryRunner = connection.createQueryRunner();
const typeormMetadataTableName = "typeorm_metadata";

const hasMetadataTable = await queryRunner.hasTable(
typeormMetadataTableName
);

expect(hasMetadataTable).to.be.false;

await connection.runMigrations();

const hasPostMigrationMetadataTable =
await queryRunner.hasTable(typeormMetadataTableName);

expect(hasPostMigrationMetadataTable).to.be.true;
})
);
});

it("should not create typeorm_metadata table when running migrations if there are no views", async () => {
connections = await createTestingConnections({
entities: [__dirname + "/entities/Foo{.js,.ts}"],
migrations: [__dirname + "/migrations/WithoutView{.js,.ts}"],
enabledDrivers: ["mysql", "mariadb"],
schemaCreate: false,
dropSchema: true,
});

await Promise.all(
connections.map(async (connection) => {
const queryRunner = connection.createQueryRunner();
const typeormMetadataTableName = "typeorm_metadata";

const hasMetadataTable = await queryRunner.hasTable(
typeormMetadataTableName
);

expect(hasMetadataTable).to.be.false;

await connection.runMigrations();

const hasPostMigrationMetadataTable =
await queryRunner.hasTable(typeormMetadataTableName);

expect(hasPostMigrationMetadataTable).to.be.false;
})
);
});
});
28 changes: 28 additions & 0 deletions test/github-issues/4956/migrations/WithView.ts
@@ -0,0 +1,28 @@
import { MigrationInterface, QueryRunner } from "../../../../src";

export class WithView1623518107000 implements MigrationInterface {
name = "WithView1623518107000";

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"CREATE TABLE `foo` (`id` int NOT NULL AUTO_INCREMENT, `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY (`id`)) ENGINE=InnoDB"
);
await queryRunner.query(
"CREATE VIEW `foo_view` AS SELECT updated_at FROM `foo`"
);
await queryRunner.query(
"INSERT INTO `typeorm_metadata`(`type`, `schema`, `name`, `value`) VALUES (?, ?, ?, ?)",
["VIEW", null, "foo_view", "SELECT `updated_at` FROM `foo`"]
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"DELETE FROM `typeorm_metadata` WHERE `type` = 'VIEW' AND `schema` = ? AND `name` = ?",
[null, "foo_view"]
);

await queryRunner.query("DROP VIEW `foo_view`");
await queryRunner.query("DROP Table `foo`");
}
}
15 changes: 15 additions & 0 deletions test/github-issues/4956/migrations/WithoutView.ts
@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from "../../../../src";

export class WithoutView1623518107000 implements MigrationInterface {
name = "WithoutView1623518107000";

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"CREATE TABLE `foo` (`id` int NOT NULL AUTO_INCREMENT, `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY (`id`)) ENGINE=InnoDB"
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("DROP Table `foo`");
}
}

0 comments on commit b2c8168

Please sign in to comment.