fix(postgres): resolve alias or table name in upsert/insert or update conditionally (#11452)

* fix: resolve regression in upsert and orUpdate for PostgreSQL driver

This fix addresses a regression introduced in
pull request #11082 by ensuring correct handling of alias names and table names when they are equal or distinct when entities use schema

Closes: #11440

* style: code formatted

* chore: run tests by running through all the drivers

* chore: set enabledDrivers as postgres

* chore: added postgres family members as enabled drivers

* chore: accepted suggestion to remove extra comments

Co-authored-by: Mike Guida <mike@mguida.com>

---------

Co-authored-by: Mike Guida <mike@mguida.com>
This commit is contained in:
Md. Minhaz Ahamed(mmarifat) 2025-06-04 21:39:38 +01:00 committed by GitHub
parent 5003aaa7c5
commit 2bfa300996
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 128 additions and 1 deletions

View File

@ -406,6 +406,10 @@ export class InsertQueryBuilder<
*/
protected createInsertExpression() {
const tableName = this.getTableName(this.getMainTableName())
const tableOrAliasName =
this.alias !== this.getMainTableName()
? this.escape(this.alias)
: tableName
const valuesExpression = this.createValuesExpression() // its important to get values before returning expression because oracle rely on native parameters and ordering of them is important
const returningExpression =
this.connection.driver.options.type === "oracle" &&
@ -590,7 +594,7 @@ export class InsertQueryBuilder<
query += overwrite
.map(
(column) =>
`${this.escape(this.alias)}.${this.escape(
`${tableOrAliasName}.${this.escape(
column,
)} IS DISTINCT FROM EXCLUDED.${this.escape(
column,

View File

@ -0,0 +1,13 @@
import { Column, Entity } from "../../../../src"
@Entity({
schema: "typeorm_test",
name: "post",
})
export class Post {
@Column({ primary: true })
id: number
@Column({ nullable: true })
title: string
}

View File

@ -0,0 +1,110 @@
import { expect } from "chai"
import "reflect-metadata"
import { DataSource } from "../../../src"
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../utils/test-utils"
import { Post } from "./entity/Post"
describe("github issues > #11440", () => {
let dataSources: DataSource[]
before(async () => {
dataSources = await createTestingConnections({
entities: [Post],
enabledDrivers: ["postgres", "aurora-postgres", "cockroachdb"],
})
})
beforeEach(() => reloadTestingDatabases(dataSources))
after(() => closeTestingConnections(dataSources))
it("should use table or alias name during upsert or doUpdate when both schema name and skipUpdateIfNoValuesChanged supplied", async () => {
Promise.all(
dataSources.map(async (dataSource) => {
const repository = dataSource.getRepository(Post)
await repository.save([
{
id: 1,
title: "First Post",
},
{
id: 2,
title: "Second Post",
},
{
id: 3,
title: "Third Post",
},
])
// upsert does not cast alias as class name
await repository.upsert(
[
{
id: 1,
title: "First Post",
},
{
id: 2,
title: "Second Post UPSERTED",
},
],
{
conflictPaths: ["id"],
upsertType: "on-conflict-do-update",
skipUpdateIfNoValuesChanged: true,
},
)
const query = repository
.createQueryBuilder()
.insert()
.values([
{
id: 1,
title: "First Post",
},
{
id: 3,
title: "Third Post OR_UPDATED",
},
])
.orUpdate(["title"], ["id"], {
skipUpdateIfNoValuesChanged: true,
})
// orUpdate cast alias as class name
expect(query.getSql()).to.equal(
`INSERT INTO "typeorm_test"."post" AS "Post"("id", "title") ` +
`VALUES ($1, $2), ($3, $4) ` +
`ON CONFLICT ( "id" ) DO UPDATE ` +
`SET "title" = EXCLUDED."title" ` +
`WHERE ("Post"."title" IS DISTINCT FROM EXCLUDED."title")`,
)
await query.execute()
const posts = await repository.find({
order: { id: "ASC" },
})
expect(posts).to.deep.equal([
{
id: 1,
title: "First Post",
},
{
id: 2,
title: "Second Post UPSERTED",
},
{
id: 3,
title: "Third Post OR_UPDATED",
},
])
}),
)
})
})