fix: resolve issues for mssql migration when simple-enum was changed

* fix: resolve issues for mssql migration when simple-enum was changed

- Changes are now detected
- Incorrect update Statement was split into a DROP CONSTRAINT and ADD CONSTRAINT Statement

Closes: #7785 #9457

* fix: resolve issues for mssql migration when simple-enum was changed

- Changes are now detected
- Incorrect update Statement was split into a DROP CONSTRAINT and ADD CONSTRAINT Statement

Closes: #7785 #9457

* code refactoring;
changed `enumName` usage to generated name;
improvements in test;

---------

Co-authored-by: ke <ke@sbs.co.at>
Co-authored-by: Alex Messer <dmzt08@gmail.com>
This commit is contained in:
ertl 2023-04-05 18:12:29 +02:00 committed by GitHub
parent 4fa14e396a
commit cb154d4ca3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 158 additions and 9 deletions

View File

@ -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) {

View File

@ -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
}

View File

@ -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 || [],
)
}

View File

@ -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
}

View File

@ -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'))`,
)
}),
))
})

View File

@ -0,0 +1,20 @@
import { MigrationInterface, QueryRunner } from "../../../../src"
export class init1676011161422 implements MigrationInterface {
name = "init1676011161422"
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
await queryRunner.query(`DROP TABLE "example_entity"`)
}
}