fix: add collation update detection in PostgresDriver (#11441)

* fix(postgres): collation not updated in DB when changed in entity

Closes: #8647

* test: issue #8647

* test: add enableDrivers, combine seperated cases

* test: update test name

* test: remove unnecessary characters in test case

* style: fix formatting

* style: fix comments typo
This commit is contained in:
DinoDeveloper 2025-06-05 22:25:43 +09:00 committed by GitHub
parent 413f0a68c8
commit 24c3e38c51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 125 additions and 1 deletions

View File

@ -1249,7 +1249,8 @@ export class PostgresDriver implements Driver {
tableColumn.srid !== columnMetadata.srid ||
tableColumn.generatedType !== columnMetadata.generatedType ||
(tableColumn.asExpression || "").trim() !==
(columnMetadata.asExpression || "").trim()
(columnMetadata.asExpression || "").trim() ||
tableColumn.collation !== columnMetadata.collation
// DEBUG SECTION
// if (isColumnChanged) {

View File

@ -2183,6 +2183,31 @@ export class PostgresQueryRunner
)
}
// update column collation
if (newColumn.collation !== oldColumn.collation) {
upQueries.push(
new Query(
`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
newColumn.name
}" TYPE ${newColumn.type} COLLATE "${
newColumn.collation
}"`,
),
)
const oldCollation = oldColumn.collation
? `"${oldColumn.collation}"`
: `pg_catalog."default"` // if there's no old collation, use default
downQueries.push(
new Query(
`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
newColumn.name
}" TYPE ${newColumn.type} COLLATE ${oldCollation}`,
),
)
}
if (newColumn.generatedType !== oldColumn.generatedType) {
// Convert generated column data to normal column
if (

View File

@ -0,0 +1,13 @@
import { Entity, PrimaryGeneratedColumn, Column } from "../../../../src/index"
export const OLD_COLLATION = "POSIX"
export const NEW_COLLATION = "C"
@Entity()
export class Item {
@PrimaryGeneratedColumn()
id: number
@Column({ type: "varchar", length: 100, collation: OLD_COLLATION })
name: string
}

View File

@ -0,0 +1,85 @@
import "reflect-metadata"
import {
createTestingConnections,
closeTestingConnections,
reloadTestingDatabases,
} from "../../utils/test-utils"
import { DataSource } from "../../../src/data-source/DataSource"
import { expect } from "chai"
import { Item, NEW_COLLATION } from "./entity/item.entity"
describe("github issues > #8647 Collation changes are not synced to RDBMS", () => {
let connections: DataSource[]
before(
async () =>
(connections = await createTestingConnections({
enabledDrivers: ["postgres"],
driverSpecific: {
applicationName: "collation-detection-test",
},
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchema: true,
})),
)
beforeEach(() => reloadTestingDatabases(connections))
after(() => closeTestingConnections(connections))
const COLUMN_NAME = "name"
it("ALTER ... COLLATE query should be created", async () => {
await Promise.all(
connections.map(async (connection) => {
// change metadata
const meta = connection.getMetadata(Item)
const col = meta.columns.find(
(c) => c.propertyName === COLUMN_NAME,
)!
const OLD_COLLATION = col.collation
col.collation = NEW_COLLATION
// capture generated up queries
const sqlInMemory = await connection.driver
.createSchemaBuilder()
.log()
const tableName = meta.tableName
const expectedUp = `ALTER TABLE "${tableName}" ALTER COLUMN "${COLUMN_NAME}" TYPE character varying COLLATE "${NEW_COLLATION}"`
const expectedDown = `ALTER TABLE "${tableName}" ALTER COLUMN "${COLUMN_NAME}" TYPE character varying COLLATE "${OLD_COLLATION}"`
// assert that the expected queries are in the generated SQL
const upJoined = sqlInMemory.upQueries
.map((q) => q.query.replace(/\s+/g, " ").trim())
.join(" ")
expect(upJoined).to.include(expectedUp)
const downJoined = sqlInMemory.downQueries
.map((q) => q.query.replace(/\s+/g, " ").trim())
.join(" ")
expect(downJoined).to.include(expectedDown)
// assert that collation changes are applied to the database
const queryRunner = connection.createQueryRunner()
try {
let table = await queryRunner.getTable(meta.tableName)
const originColumn = table!.columns.find(
(c) => c.name === COLUMN_NAME,
)!
// old collation should be appeared
expect(originColumn.collation).to.equal(OLD_COLLATION)
await connection.synchronize()
table = await queryRunner.getTable(meta.tableName)
const appliedColumn = table!.columns.find(
(c) => c.name === COLUMN_NAME,
)!
// new collation should be appeared
expect(appliedColumn.collation).to.equal(NEW_COLLATION)
} finally {
await queryRunner.release()
}
}),
)
})
})