diff --git a/src/driver/sqlserver/SqlServerDriver.ts b/src/driver/sqlserver/SqlServerDriver.ts index 768a2c6b3..e35c9818b 100644 --- a/src/driver/sqlserver/SqlServerDriver.ts +++ b/src/driver/sqlserver/SqlServerDriver.ts @@ -795,7 +795,14 @@ export class SqlServerDriver implements Driver { tableColumn.isNullable !== columnMetadata.isNullable || tableColumn.asExpression !== columnMetadata.asExpression || tableColumn.generatedType !== columnMetadata.generatedType || - tableColumn.isUnique !== this.normalizeIsUnique(columnMetadata) + tableColumn.isUnique !== + this.normalizeIsUnique(columnMetadata) || + (tableColumn.enum && + columnMetadata.enum && + !OrmUtils.isArraysEqual( + tableColumn.enum, + columnMetadata.enum.map((val) => val + ""), + )) // DEBUG SECTION // if (isColumnChanged) { diff --git a/src/driver/sqlserver/SqlServerQueryRunner.ts b/src/driver/sqlserver/SqlServerQueryRunner.ts index 2779a1dd1..c320b184b 100644 --- a/src/driver/sqlserver/SqlServerQueryRunner.ts +++ b/src/driver/sqlserver/SqlServerQueryRunner.ts @@ -1566,7 +1566,9 @@ export class SqlServerQueryRunner oldColumn.name = newColumn.name } - if (this.isColumnChanged(oldColumn, newColumn, false)) { + if ( + this.isColumnChanged(oldColumn, newColumn, false, false, false) + ) { upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1576,6 +1578,7 @@ export class SqlServerQueryRunner newColumn, true, false, + true, )}`, ), ) @@ -1588,11 +1591,40 @@ export class SqlServerQueryRunner oldColumn, true, false, + true, )}`, ), ) } + if (this.isEnumChanged(oldColumn, newColumn)) { + const oldExpression = this.getEnumExpression(oldColumn) + const oldCheck = new TableCheck({ + name: this.connection.namingStrategy.checkConstraintName( + table, + oldExpression, + true, + ), + expression: oldExpression, + }) + + const newExpression = this.getEnumExpression(newColumn) + const newCheck = new TableCheck({ + name: this.connection.namingStrategy.checkConstraintName( + table, + newExpression, + true, + ), + expression: newExpression, + }) + + upQueries.push(this.dropCheckConstraintSql(table, oldCheck)) + upQueries.push(this.createCheckConstraintSql(table, newCheck)) + + downQueries.push(this.dropCheckConstraintSql(table, newCheck)) + downQueries.push(this.createCheckConstraintSql(table, oldCheck)) + } + if (newColumn.isPrimary !== oldColumn.isPrimary) { const primaryColumns = clonedTable.primaryColumns @@ -3904,17 +3936,14 @@ export class SqlServerQueryRunner column: TableColumn, skipIdentity: boolean, createDefault: boolean, + skipEnum?: boolean, ) { let c = `"${column.name}" ${this.connection.driver.createFullType( column, )}` - if (column.enum) { - const expression = - column.name + - " IN (" + - column.enum.map((val) => "'" + val + "'").join(",") + - ")" + if (!skipEnum && column.enum) { + const expression = this.getEnumExpression(column) const checkName = this.connection.namingStrategy.checkConstraintName( table, @@ -3976,6 +4005,18 @@ export class SqlServerQueryRunner return c } + private getEnumExpression(column: TableColumn) { + if (!column.enum) { + throw new Error(`Enum is not defined in column ${column.name}`) + } + return ( + column.name + + " IN (" + + column.enum.map((val) => "'" + val + "'").join(",") + + ")" + ) + } + protected isEnumCheckConstraint(name: string): boolean { return name.indexOf("CHK_") !== -1 && name.indexOf("_ENUM") !== -1 } diff --git a/src/query-runner/BaseQueryRunner.ts b/src/query-runner/BaseQueryRunner.ts index 740ac8374..466e6160f 100644 --- a/src/query-runner/BaseQueryRunner.ts +++ b/src/query-runner/BaseQueryRunner.ts @@ -472,6 +472,7 @@ export abstract class BaseQueryRunner { newColumn: TableColumn, checkDefault?: boolean, checkComment?: boolean, + checkEnum = true, ): boolean { // this logs need to debug issues in column change detection. Do not delete it! @@ -513,7 +514,14 @@ export abstract class BaseQueryRunner { oldColumn.onUpdate !== newColumn.onUpdate || // MySQL only oldColumn.isNullable !== newColumn.isNullable || (checkComment && oldColumn.comment !== newColumn.comment) || - !OrmUtils.isArraysEqual(oldColumn.enum || [], newColumn.enum || []) + (checkEnum && this.isEnumChanged(oldColumn, newColumn)) + ) + } + + protected isEnumChanged(oldColumn: TableColumn, newColumn: TableColumn) { + return !OrmUtils.isArraysEqual( + oldColumn.enum || [], + newColumn.enum || [], ) } diff --git a/test/github-issues/9457/entity/ExampleEntity.ts b/test/github-issues/9457/entity/ExampleEntity.ts new file mode 100644 index 000000000..d4893722a --- /dev/null +++ b/test/github-issues/9457/entity/ExampleEntity.ts @@ -0,0 +1,25 @@ +import { Entity } from "../../../../src/decorator/entity/Entity" +import { Column } from "../../../../src/decorator/columns/Column" +import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn" +import { Generated } from "../../../../src" + +export enum ExampleEnum { + EnumValue1 = "enumvalue1", + EnumValue2 = "enumvalue2", + EnumValue3 = "enumvalue3", + EnumValue4 = "enumvalue4", +} + +@Entity() +export class ExampleEntity { + @Generated("increment") + @PrimaryGeneratedColumn() + id: number + + @Column({ + length: 255, + type: "simple-enum", + enum: ExampleEnum, + }) + enumcolumn: ExampleEnum +} diff --git a/test/github-issues/9457/issue-9457.ts b/test/github-issues/9457/issue-9457.ts new file mode 100644 index 000000000..41dff9fdc --- /dev/null +++ b/test/github-issues/9457/issue-9457.ts @@ -0,0 +1,48 @@ +import "reflect-metadata" +import { + createTestingConnections, + closeTestingConnections, +} from "../../utils/test-utils" +import { DataSource } from "../../../src/data-source/DataSource" +import { expect } from "chai" + +describe("github issues > #9457 No changes in database schema were found, when simple-enum is changed.", () => { + let dataSources: DataSource[] + before(async () => { + dataSources = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + migrations: [__dirname + "/migration/*{.js,.ts}"], + schemaCreate: false, + dropSchema: true, + enabledDrivers: ["mssql"], + }) + }) + after(() => closeTestingConnections(dataSources)) + + it("should drop and recreate 'CHECK' constraint to match enum values", () => + Promise.all( + dataSources.map(async (dataSource) => { + await dataSource.runMigrations() + + const sqlInMemory = await dataSource.driver + .createSchemaBuilder() + .log() + + expect(sqlInMemory.upQueries.length).to.eql(2) + expect(sqlInMemory.upQueries[0].query).to.eql( + 'ALTER TABLE "example_entity" DROP CONSTRAINT "CHK_a80c9d6a2a8749d7aadb857dc6_ENUM"', + ) + expect(sqlInMemory.upQueries[1].query).to.eql( + `ALTER TABLE "example_entity" ADD CONSTRAINT "CHK_be8ed063b3976da24df4213baf_ENUM" CHECK (enumcolumn IN ('enumvalue1','enumvalue2','enumvalue3','enumvalue4'))`, + ) + + expect(sqlInMemory.downQueries.length).to.eql(2) + expect(sqlInMemory.downQueries[0].query).to.eql( + 'ALTER TABLE "example_entity" DROP CONSTRAINT "CHK_be8ed063b3976da24df4213baf_ENUM"', + ) + expect(sqlInMemory.downQueries[1].query).to.eql( + `ALTER TABLE "example_entity" ADD CONSTRAINT "CHK_a80c9d6a2a8749d7aadb857dc6_ENUM" CHECK (enumcolumn IN ('enumvalue1','enumvalue2','enumvalue3'))`, + ) + }), + )) +}) diff --git a/test/github-issues/9457/migration/1676011161422-init.ts b/test/github-issues/9457/migration/1676011161422-init.ts new file mode 100644 index 000000000..22c5dec98 --- /dev/null +++ b/test/github-issues/9457/migration/1676011161422-init.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "../../../../src" + +export class init1676011161422 implements MigrationInterface { + name = "init1676011161422" + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "example_entity" + ( + "id" int NOT NULL IDENTITY(1,1), + "enumcolumn" nvarchar(255) CONSTRAINT CHK_a80c9d6a2a8749d7aadb857dc6_ENUM CHECK (enumcolumn IN ('enumvalue1','enumvalue2','enumvalue3')) NOT NULL, + CONSTRAINT "PK_fccd73330168066a434dbac114f" PRIMARY KEY ("id") + )`, + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "example_entity"`) + } +}