Skip to content

Commit 24c3e38

Browse files
authored
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
1 parent 413f0a6 commit 24c3e38

File tree

4 files changed

+125
-1
lines changed

4 files changed

+125
-1
lines changed

src/driver/postgres/PostgresDriver.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1249,7 +1249,8 @@ export class PostgresDriver implements Driver {
12491249
tableColumn.srid !== columnMetadata.srid ||
12501250
tableColumn.generatedType !== columnMetadata.generatedType ||
12511251
(tableColumn.asExpression || "").trim() !==
1252-
(columnMetadata.asExpression || "").trim()
1252+
(columnMetadata.asExpression || "").trim() ||
1253+
tableColumn.collation !== columnMetadata.collation
12531254

12541255
// DEBUG SECTION
12551256
// if (isColumnChanged) {

src/driver/postgres/PostgresQueryRunner.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2183,6 +2183,31 @@ export class PostgresQueryRunner
21832183
)
21842184
}
21852185

2186+
// update column collation
2187+
if (newColumn.collation !== oldColumn.collation) {
2188+
upQueries.push(
2189+
new Query(
2190+
`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
2191+
newColumn.name
2192+
}" TYPE ${newColumn.type} COLLATE "${
2193+
newColumn.collation
2194+
}"`,
2195+
),
2196+
)
2197+
2198+
const oldCollation = oldColumn.collation
2199+
? `"${oldColumn.collation}"`
2200+
: `pg_catalog."default"` // if there's no old collation, use default
2201+
2202+
downQueries.push(
2203+
new Query(
2204+
`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
2205+
newColumn.name
2206+
}" TYPE ${newColumn.type} COLLATE ${oldCollation}`,
2207+
),
2208+
)
2209+
}
2210+
21862211
if (newColumn.generatedType !== oldColumn.generatedType) {
21872212
// Convert generated column data to normal column
21882213
if (
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Entity, PrimaryGeneratedColumn, Column } from "../../../../src/index"
2+
3+
export const OLD_COLLATION = "POSIX"
4+
export const NEW_COLLATION = "C"
5+
6+
@Entity()
7+
export class Item {
8+
@PrimaryGeneratedColumn()
9+
id: number
10+
11+
@Column({ type: "varchar", length: 100, collation: OLD_COLLATION })
12+
name: string
13+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import "reflect-metadata"
2+
import {
3+
createTestingConnections,
4+
closeTestingConnections,
5+
reloadTestingDatabases,
6+
} from "../../utils/test-utils"
7+
import { DataSource } from "../../../src/data-source/DataSource"
8+
import { expect } from "chai"
9+
import { Item, NEW_COLLATION } from "./entity/item.entity"
10+
11+
describe("github issues > #8647 Collation changes are not synced to RDBMS", () => {
12+
let connections: DataSource[]
13+
14+
before(
15+
async () =>
16+
(connections = await createTestingConnections({
17+
enabledDrivers: ["postgres"],
18+
driverSpecific: {
19+
applicationName: "collation-detection-test",
20+
},
21+
entities: [__dirname + "/entity/*{.js,.ts}"],
22+
schemaCreate: true,
23+
dropSchema: true,
24+
})),
25+
)
26+
beforeEach(() => reloadTestingDatabases(connections))
27+
after(() => closeTestingConnections(connections))
28+
29+
const COLUMN_NAME = "name"
30+
31+
it("ALTER ... COLLATE query should be created", async () => {
32+
await Promise.all(
33+
connections.map(async (connection) => {
34+
// change metadata
35+
const meta = connection.getMetadata(Item)
36+
const col = meta.columns.find(
37+
(c) => c.propertyName === COLUMN_NAME,
38+
)!
39+
const OLD_COLLATION = col.collation
40+
col.collation = NEW_COLLATION
41+
42+
// capture generated up queries
43+
const sqlInMemory = await connection.driver
44+
.createSchemaBuilder()
45+
.log()
46+
const tableName = meta.tableName
47+
const expectedUp = `ALTER TABLE "${tableName}" ALTER COLUMN "${COLUMN_NAME}" TYPE character varying COLLATE "${NEW_COLLATION}"`
48+
const expectedDown = `ALTER TABLE "${tableName}" ALTER COLUMN "${COLUMN_NAME}" TYPE character varying COLLATE "${OLD_COLLATION}"`
49+
50+
// assert that the expected queries are in the generated SQL
51+
const upJoined = sqlInMemory.upQueries
52+
.map((q) => q.query.replace(/\s+/g, " ").trim())
53+
.join(" ")
54+
expect(upJoined).to.include(expectedUp)
55+
const downJoined = sqlInMemory.downQueries
56+
.map((q) => q.query.replace(/\s+/g, " ").trim())
57+
.join(" ")
58+
expect(downJoined).to.include(expectedDown)
59+
60+
// assert that collation changes are applied to the database
61+
const queryRunner = connection.createQueryRunner()
62+
63+
try {
64+
let table = await queryRunner.getTable(meta.tableName)
65+
const originColumn = table!.columns.find(
66+
(c) => c.name === COLUMN_NAME,
67+
)!
68+
// old collation should be appeared
69+
expect(originColumn.collation).to.equal(OLD_COLLATION)
70+
71+
await connection.synchronize()
72+
73+
table = await queryRunner.getTable(meta.tableName)
74+
const appliedColumn = table!.columns.find(
75+
(c) => c.name === COLUMN_NAME,
76+
)!
77+
// new collation should be appeared
78+
expect(appliedColumn.collation).to.equal(NEW_COLLATION)
79+
} finally {
80+
await queryRunner.release()
81+
}
82+
}),
83+
)
84+
})
85+
})

0 commit comments

Comments
 (0)