From 6679bef560ab847ad6910a4ba8630b5c6f81b60c Mon Sep 17 00:00:00 2001 From: Matteias Collet Date: Wed, 23 Oct 2019 14:02:26 +0200 Subject: [PATCH 1/9] fix: create typeorm_metatable when running migrations --- src/commands/MigrationRunCommand.ts | 7 +++++++ src/schema-builder/RdbmsSchemaBuilder.ts | 20 ++++++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/commands/MigrationRunCommand.ts b/src/commands/MigrationRunCommand.ts index e27915fe17..58fedc2c1f 100644 --- a/src/commands/MigrationRunCommand.ts +++ b/src/commands/MigrationRunCommand.ts @@ -3,6 +3,7 @@ import {ConnectionOptionsReader} from "../connection/ConnectionOptionsReader"; import {Connection} from "../connection/Connection"; import * as process from "process"; import * as yargs from "yargs"; +import {RdbmsSchemaBuilder} from "../schema-builder/RdbmsSchemaBuilder"; const chalk = require("chalk"); /** @@ -73,6 +74,12 @@ export class MigrationRunCommand implements yargs.CommandModule { // noop } + const schemaBuilder = connection.driver.createSchemaBuilder(); + + if (schemaBuilder instanceof RdbmsSchemaBuilder) { + await schemaBuilder.createMetadataTableIfNecessary(); + } + await connection.runMigrations(options); await connection.close(); // exit process if no errors diff --git a/src/schema-builder/RdbmsSchemaBuilder.ts b/src/schema-builder/RdbmsSchemaBuilder.ts index 7653513d28..64603e4f46 100644 --- a/src/schema-builder/RdbmsSchemaBuilder.ts +++ b/src/schema-builder/RdbmsSchemaBuilder.ts @@ -70,10 +70,7 @@ export class RdbmsSchemaBuilder implements SchemaBuilder { await this.queryRunner.startTransaction(); try { const tablePaths = this.entityToSyncMetadatas.map(metadata => metadata.tablePath); - // 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(); + await this.createMetadataTableIfNecessary(); await this.queryRunner.getTables(tablePaths); await this.queryRunner.getViews([]); await this.executeSchemaSyncOperationsInProperOrder(); @@ -98,6 +95,16 @@ export class RdbmsSchemaBuilder implements SchemaBuilder { } } + /** + * If the schema contains views, create the typeorm_metadata table if it doesn't exist yet + */ + async createMetadataTableIfNecessary(): Promise { + if (!this.queryRunner) this.queryRunner = this.connection.createQueryRunner("master"); + if (this.viewEntityToSyncMetadatas.length > 0) { + await this.createTypeormMetadataTable(); + } + } + /** * Returns sql queries to be executed by schema builder. */ @@ -105,10 +112,7 @@ export class RdbmsSchemaBuilder implements SchemaBuilder { this.queryRunner = this.connection.createQueryRunner("master"); try { const tablePaths = this.entityToSyncMetadatas.map(metadata => metadata.tablePath); - // 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(); + await this.createMetadataTableIfNecessary(); await this.queryRunner.getTables(tablePaths); await this.queryRunner.getViews([]); this.queryRunner.enableSqlMemory(); From d4875b9a248f493fa9c965f1b550d0895c3cc05a Mon Sep 17 00:00:00 2001 From: Matteias Collet Date: Fri, 22 Nov 2019 09:04:36 +0100 Subject: [PATCH 2/9] remove redundant check --- src/schema-builder/RdbmsSchemaBuilder.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/schema-builder/RdbmsSchemaBuilder.ts b/src/schema-builder/RdbmsSchemaBuilder.ts index 64603e4f46..1a32e511d2 100644 --- a/src/schema-builder/RdbmsSchemaBuilder.ts +++ b/src/schema-builder/RdbmsSchemaBuilder.ts @@ -99,7 +99,6 @@ export class RdbmsSchemaBuilder implements SchemaBuilder { * If the schema contains views, create the typeorm_metadata table if it doesn't exist yet */ async createMetadataTableIfNecessary(): Promise { - if (!this.queryRunner) this.queryRunner = this.connection.createQueryRunner("master"); if (this.viewEntityToSyncMetadatas.length > 0) { await this.createTypeormMetadataTable(); } From f1f3d36f1b82107b49982043312d7b9644d2af28 Mon Sep 17 00:00:00 2001 From: Matteias Collet Date: Fri, 22 Nov 2019 13:25:55 +0100 Subject: [PATCH 3/9] create typeorm_metadata table in MigrationExecutor --- src/migration/MigrationExecutor.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/migration/MigrationExecutor.ts b/src/migration/MigrationExecutor.ts index aef39b0670..0840e30a29 100644 --- a/src/migration/MigrationExecutor.ts +++ b/src/migration/MigrationExecutor.ts @@ -10,6 +10,7 @@ import {SqlServerConnectionOptions} from "../driver/sqlserver/SqlServerConnectio import {PostgresConnectionOptions} from "../driver/postgres/PostgresConnectionOptions"; import { MongoDriver } from "../driver/mongodb/MongoDriver"; import { MongoQueryRunner } from "../driver/mongodb/MongoQueryRunner"; +import { RdbmsSchemaBuilder } from "../schema-builder/RdbmsSchemaBuilder"; /** * Executes migrations: runs pending and reverts previously executed migrations. @@ -198,6 +199,13 @@ export class MigrationExecutor { // 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(); + } + // get all migrations that are executed and saved in the database const executedMigrations = await this.loadExecutedMigrations(queryRunner); From 4b8592c6a0c87f127bcb1043590d394454b7edc5 Mon Sep 17 00:00:00 2001 From: Matteias Collet Date: Sun, 24 Nov 2019 10:42:38 +0100 Subject: [PATCH 4/9] remove redundant call to createMetadataTableIfNecessary --- src/commands/MigrationRunCommand.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/commands/MigrationRunCommand.ts b/src/commands/MigrationRunCommand.ts index 58fedc2c1f..e27915fe17 100644 --- a/src/commands/MigrationRunCommand.ts +++ b/src/commands/MigrationRunCommand.ts @@ -3,7 +3,6 @@ import {ConnectionOptionsReader} from "../connection/ConnectionOptionsReader"; import {Connection} from "../connection/Connection"; import * as process from "process"; import * as yargs from "yargs"; -import {RdbmsSchemaBuilder} from "../schema-builder/RdbmsSchemaBuilder"; const chalk = require("chalk"); /** @@ -74,12 +73,6 @@ export class MigrationRunCommand implements yargs.CommandModule { // noop } - const schemaBuilder = connection.driver.createSchemaBuilder(); - - if (schemaBuilder instanceof RdbmsSchemaBuilder) { - await schemaBuilder.createMetadataTableIfNecessary(); - } - await connection.runMigrations(options); await connection.close(); // exit process if no errors From ec5a4f2a03174d1894b1c103975902b953eb136c Mon Sep 17 00:00:00 2001 From: Matteias Collet Date: Thu, 5 Dec 2019 12:29:04 +0100 Subject: [PATCH 5/9] move create metadata table logic --- src/migration/MigrationExecutor.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/migration/MigrationExecutor.ts b/src/migration/MigrationExecutor.ts index 0840e30a29..0d637f497f 100644 --- a/src/migration/MigrationExecutor.ts +++ b/src/migration/MigrationExecutor.ts @@ -95,6 +95,14 @@ export class MigrationExecutor { const queryRunner = this.queryRunner || this.connection.createQueryRunner("master"); // 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(); + } + // get all migrations that are executed and saved in the database const executedMigrations = await this.loadExecutedMigrations(queryRunner); @@ -199,13 +207,6 @@ export class MigrationExecutor { // 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(); - } - // get all migrations that are executed and saved in the database const executedMigrations = await this.loadExecutedMigrations(queryRunner); From 420dcbb07ff3079c9b15b0f792fdfee0fb95c4c9 Mon Sep 17 00:00:00 2001 From: Matteias Collet Date: Sat, 12 Jun 2021 20:52:07 +0200 Subject: [PATCH 6/9] add missing test (#4956) --- src/schema-builder/RdbmsSchemaBuilder.ts | 1 + test/github-issues/4956/entities/Foo.ts | 12 +++ test/github-issues/4956/entities/FooView.ts | 13 ++++ test/github-issues/4956/issue-4956.ts | 78 +++++++++++++++++++ .../github-issues/4956/migrations/WithView.ts | 28 +++++++ .../4956/migrations/WithoutView.ts | 15 ++++ 6 files changed, 147 insertions(+) create mode 100644 test/github-issues/4956/entities/Foo.ts create mode 100644 test/github-issues/4956/entities/FooView.ts create mode 100644 test/github-issues/4956/issue-4956.ts create mode 100644 test/github-issues/4956/migrations/WithView.ts create mode 100644 test/github-issues/4956/migrations/WithoutView.ts diff --git a/src/schema-builder/RdbmsSchemaBuilder.ts b/src/schema-builder/RdbmsSchemaBuilder.ts index 063b04c555..5abc1e4b7b 100644 --- a/src/schema-builder/RdbmsSchemaBuilder.ts +++ b/src/schema-builder/RdbmsSchemaBuilder.ts @@ -101,6 +101,7 @@ export class RdbmsSchemaBuilder implements SchemaBuilder { */ async createMetadataTableIfNecessary(): Promise { if (this.viewEntityToSyncMetadatas.length > 0) { + this.queryRunner = this.queryRunner || this.connection.createQueryRunner(); await this.createTypeormMetadataTable(); } } diff --git a/test/github-issues/4956/entities/Foo.ts b/test/github-issues/4956/entities/Foo.ts new file mode 100644 index 0000000000..1f6ac1f9fc --- /dev/null +++ b/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; +} diff --git a/test/github-issues/4956/entities/FooView.ts b/test/github-issues/4956/entities/FooView.ts new file mode 100644 index 0000000000..bfe5821f4d --- /dev/null +++ b/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; +} diff --git a/test/github-issues/4956/issue-4956.ts b/test/github-issues/4956/issue-4956.ts new file mode 100644 index 0000000000..c1a9e89061 --- /dev/null +++ b/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/WithoutViews{.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; + }) + ); + }); +}); diff --git a/test/github-issues/4956/migrations/WithView.ts b/test/github-issues/4956/migrations/WithView.ts new file mode 100644 index 0000000000..38e01c3410 --- /dev/null +++ b/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 { + 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 { + 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`"); + } +} diff --git a/test/github-issues/4956/migrations/WithoutView.ts b/test/github-issues/4956/migrations/WithoutView.ts new file mode 100644 index 0000000000..47b5ac2319 --- /dev/null +++ b/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 { + 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 { + await queryRunner.query("DROP Table `foo`"); + } +} From 29c1d4f4188b779a4359c217a5cd04c371729f9d Mon Sep 17 00:00:00 2001 From: Matteias Collet Date: Sat, 12 Jun 2021 21:02:28 +0200 Subject: [PATCH 7/9] fix migration name --- test/github-issues/4956/issue-4956.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/github-issues/4956/issue-4956.ts b/test/github-issues/4956/issue-4956.ts index c1a9e89061..95e5661920 100644 --- a/test/github-issues/4956/issue-4956.ts +++ b/test/github-issues/4956/issue-4956.ts @@ -49,7 +49,7 @@ describe("github issues > #4956 create typeorm_metatable when running migrations 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/WithoutViews{.js,.ts}"], + migrations: [__dirname + "/migrations/WithoutView{.js,.ts}"], enabledDrivers: ["mysql", "mariadb"], schemaCreate: false, dropSchema: true, From 6dd2b599e50ef3b461d7eb05138cbb604d77ec13 Mon Sep 17 00:00:00 2001 From: Matteias Collet Date: Wed, 10 Nov 2021 19:05:59 +0100 Subject: [PATCH 8/9] pass query runner instance to metadata creation --- src/migration/MigrationExecutor.ts | 2 +- src/schema-builder/RdbmsSchemaBuilder.ts | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/migration/MigrationExecutor.ts b/src/migration/MigrationExecutor.ts index e31bf9bbc3..c3700a7b0d 100644 --- a/src/migration/MigrationExecutor.ts +++ b/src/migration/MigrationExecutor.ts @@ -163,7 +163,7 @@ export class MigrationExecutor { const schemaBuilder = this.connection.driver.createSchemaBuilder(); if (schemaBuilder instanceof RdbmsSchemaBuilder) { - await schemaBuilder.createMetadataTableIfNecessary(); + await schemaBuilder.createMetadataTableIfNecessary(queryRunner); } // get all migrations that are executed and saved in the database diff --git a/src/schema-builder/RdbmsSchemaBuilder.ts b/src/schema-builder/RdbmsSchemaBuilder.ts index 85e6ab6c2d..4aa23b2233 100644 --- a/src/schema-builder/RdbmsSchemaBuilder.ts +++ b/src/schema-builder/RdbmsSchemaBuilder.ts @@ -77,10 +77,10 @@ export class RdbmsSchemaBuilder implements SchemaBuilder { } try { + await this.createMetadataTableIfNecessary(this.queryRunner); + // Flush the queryrunner table & view cache - const tablePaths = this.entityToSyncMetadatas.map(metadata => metadata.tablePath); - await this.createMetadataTableIfNecessary(); - + const tablePaths = this.entityToSyncMetadatas.map(metadata => this.getTablePath(metadata)); await this.queryRunner.getTables(tablePaths); await this.queryRunner.getViews([]); @@ -111,10 +111,9 @@ export class RdbmsSchemaBuilder implements SchemaBuilder { /** * If the schema contains views, create the typeorm_metadata table if it doesn't exist yet */ - async createMetadataTableIfNecessary(): Promise { + async createMetadataTableIfNecessary(queryRunner: QueryRunner): Promise { if (this.viewEntityToSyncMetadatas.length > 0) { - this.queryRunner = this.queryRunner || this.connection.createQueryRunner(); - await this.createTypeormMetadataTable(); + await this.createTypeormMetadataTable(queryRunner); } } @@ -124,10 +123,10 @@ export class RdbmsSchemaBuilder implements SchemaBuilder { async log(): Promise { this.queryRunner = this.connection.createQueryRunner(); try { + await this.createMetadataTableIfNecessary(this.queryRunner); + // Flush the queryrunner table & view cache const tablePaths = this.entityToSyncMetadatas.map(metadata => this.getTablePath(metadata)); - await this.createMetadataTableIfNecessary(); - await this.queryRunner.getTables(tablePaths); await this.queryRunner.getViews([]); @@ -833,12 +832,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, From 54ced472901f77bd3824bbd613c59822d1a908d4 Mon Sep 17 00:00:00 2001 From: Matteias Collet Date: Thu, 11 Nov 2021 17:26:31 +0100 Subject: [PATCH 9/9] 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 --- src/schema-builder/RdbmsSchemaBuilder.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/schema-builder/RdbmsSchemaBuilder.ts b/src/schema-builder/RdbmsSchemaBuilder.ts index 4aa23b2233..d6fc04d8ea 100644 --- a/src/schema-builder/RdbmsSchemaBuilder.ts +++ b/src/schema-builder/RdbmsSchemaBuilder.ts @@ -123,8 +123,6 @@ export class RdbmsSchemaBuilder implements SchemaBuilder { async log(): Promise { this.queryRunner = this.connection.createQueryRunner(); try { - await this.createMetadataTableIfNecessary(this.queryRunner); - // Flush the queryrunner table & view cache const tablePaths = this.entityToSyncMetadatas.map(metadata => this.getTablePath(metadata)); await this.queryRunner.getTables(tablePaths);