fix: fix up aggregate methods ambiguous column (#11822)

This commit is contained in:
Prakhar Chhalotre 2025-12-05 03:19:31 +05:30 committed by GitHub
parent 73fda419e4
commit 6e34756b9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 329 additions and 4 deletions

View File

@ -1090,12 +1090,16 @@ export class EntityManager {
)
}
const result = await this.createQueryBuilder(entityClass, metadata.name)
.setFindOptions({ where })
const qb = this.createQueryBuilder(entityClass, metadata.name)
qb.setFindOptions({ where })
const alias = qb.alias
const result = await qb
.select(
`${fnName}(${this.connection.driver.escape(
column.databaseName,
)})`,
alias,
)}.${this.connection.driver.escape(column.databaseName)})`,
fnName,
)
.getRawOne()

View File

@ -0,0 +1,19 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToMany,
} from "../../../../../src"
import { Post } from "./Post"
@Entity()
export class Author {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@OneToMany(() => Post, (post) => post.author)
posts: Post[]
}

View File

@ -0,0 +1,22 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
} from "../../../../../src"
import { Author } from "./Author"
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
viewCount: number
@ManyToOne(() => Author, (author) => author.posts)
author: Author
}

View File

@ -0,0 +1,280 @@
import "reflect-metadata"
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../../utils/test-utils"
import { DataSource } from "../../../../src"
import { Post } from "./entity/Post"
import { Author } from "./entity/Author"
import { expect } from "chai"
describe("repository > aggregate methods with relations", () => {
let connections: DataSource[]
before(
async () =>
(connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchema: true,
})),
)
beforeEach(() => reloadTestingDatabases(connections))
after(() => closeTestingConnections(connections))
describe("sum with relation filter", () => {
it("should return the aggregate sum when filtering by relation", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "Author 1",
})
const author2 = await authorRepo.save({
name: "Author 2",
})
await postRepo.save([
{ title: "Post 1", viewCount: 100, author: author1 },
{ title: "Post 2", viewCount: 200, author: author1 },
{ title: "Post 3", viewCount: 300, author: author2 },
])
const sum = await postRepo.sum("viewCount", {
author: { id: author1.id },
})
expect(sum).to.equal(300)
}),
))
it("should return null when no records match relation filter", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "Author 1",
})
const sum = await postRepo.sum("viewCount", {
author: { id: author1.id },
})
expect(sum).to.be.equal(null)
}),
))
})
describe("average with relation filter", () => {
it("should return the aggregate average when filtering by relation", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "Author 1",
})
const author2 = await authorRepo.save({
name: "Author 2",
})
await postRepo.save([
{ title: "Post 1", viewCount: 100, author: author1 },
{ title: "Post 2", viewCount: 200, author: author1 },
{ title: "Post 3", viewCount: 300, author: author2 },
])
const average = await postRepo.average("viewCount", {
author: { id: author1.id },
})
expect(average).to.equal(150)
}),
))
it("should return null when no records match relation filter", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "Author 1",
})
const average = await postRepo.average("viewCount", {
author: { id: author1.id },
})
expect(average).to.be.equal(null)
}),
))
})
describe("minimum with relation filter", () => {
it("should return the aggregate minimum when filtering by relation", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "Author 1",
})
const author2 = await authorRepo.save({
name: "Author 2",
})
await postRepo.save([
{ title: "Post 1", viewCount: 100, author: author1 },
{ title: "Post 2", viewCount: 200, author: author1 },
{ title: "Post 3", viewCount: 50, author: author2 },
])
const minimum = await postRepo.minimum("viewCount", {
author: { id: author1.id },
})
expect(minimum).to.equal(100)
}),
))
it("should return null when no records match relation filter", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "Author 1",
})
const minimum = await postRepo.minimum("viewCount", {
author: { id: author1.id },
})
expect(minimum).to.be.equal(null)
}),
))
})
describe("maximum with relation filter", () => {
it("should return the aggregate maximum when filtering by relation", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "Author 1",
})
const author2 = await authorRepo.save({
name: "Author 2",
})
await postRepo.save([
{ title: "Post 1", viewCount: 100, author: author1 },
{ title: "Post 2", viewCount: 200, author: author1 },
{ title: "Post 3", viewCount: 500, author: author2 },
])
const maximum = await postRepo.maximum("viewCount", {
author: { id: author1.id },
})
expect(maximum).to.equal(200)
}),
))
it("should return null when no records match relation filter", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "Author 1",
})
const maximum = await postRepo.maximum("viewCount", {
author: { id: author1.id },
})
expect(maximum).to.be.equal(null)
}),
))
})
describe("aggregate methods with nested relation filters", () => {
it("should handle complex relation filters correctly", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "John Doe",
})
const author2 = await authorRepo.save({
name: "Jane Smith",
})
await postRepo.save([
{ title: "Post 1", viewCount: 100, author: author1 },
{ title: "Post 2", viewCount: 200, author: author1 },
{ title: "Post 3", viewCount: 300, author: author2 },
])
// Filter by both relation id and relation property
const sum = await postRepo.sum("viewCount", {
author: { id: author1.id, name: "John Doe" },
})
expect(sum).to.equal(300)
}),
))
})
describe("aggregate methods with multiple tables having same column name", () => {
it("should correctly qualify column names to avoid ambiguous references", () =>
Promise.all(
connections.map(async (connection) => {
const authorRepo = connection.getRepository(Author)
const postRepo = connection.getRepository(Post)
const author1 = await authorRepo.save({
name: "Author 1",
})
const author2 = await authorRepo.save({
name: "Author 2",
})
await postRepo.save([
{ title: "Post 1", viewCount: 100, author: author1 },
{ title: "Post 2", viewCount: 200, author: author1 },
{ title: "Post 3", viewCount: 300, author: author2 },
])
// Both Post and Author have 'id' column - this should not cause ambiguous column error
const maxId = await postRepo.maximum("id", {
author: { id: author1.id },
})
expect(maxId).to.be.a("number")
expect(maxId).to.be.greaterThan(0)
// Verify we got the correct max ID from author1's posts, not from author2 or any other table
const author1Posts = await postRepo.find({
where: { author: { id: author1.id } },
order: { id: "DESC" },
})
expect(maxId).to.equal(author1Posts[0].id)
}),
))
})
})