Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ordering by joined columns for PostgreSQL (#3736) #8118

Merged
merged 1 commit into from
Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions src/query-builder/SelectQueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1951,7 +1951,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements

// we are skipping order by here because its not working in subqueries anyway
// to make order by working we need to apply it on a distinct query
const [selects, orderBys] = this.createOrderByCombinedWithSelectExpression("distinctAlias");
const [selects, orderBys, subquerySelect] = this.createOrderByCombinedWithSelectExpression("distinctAlias");
const metadata = this.expressionMap.mainAlias.metadata;
const mainAliasName = this.expressionMap.mainAlias.name;

Expand All @@ -1973,7 +1973,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
rawResults = await new SelectQueryBuilder(this.connection, queryRunner)
.select(`DISTINCT ${querySelects.join(", ")}`)
.addSelect(selects)
.from(`(${this.clone().orderBy().getQuery()})`, "distinctAlias")
.from(`(${this.clone().orderBy().addSelect(subquerySelect).getQuery()})`, "distinctAlias")
.offset(this.expressionMap.skip)
.limit(this.expressionMap.take)
.orderBy(orderBys)
Expand Down Expand Up @@ -2042,7 +2042,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
};
}

protected createOrderByCombinedWithSelectExpression(parentAlias: string): [ string, OrderByCondition] {
protected createOrderByCombinedWithSelectExpression(parentAlias: string): [ string, OrderByCondition, string] {

// if table has a default order then apply it
const orderBys = this.expressionMap.allOrderBys;
Expand Down Expand Up @@ -2082,7 +2082,24 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
}
});

return [selectString, orderByObject];
const subquerySelectString = Object.keys(orderBys)
.filter(orderCriteria => orderCriteria.includes("."))
.map(orderCriteria => {
const criteriaParts = orderCriteria.split(".");
const aliasName = criteriaParts[0];
const propertyPath = criteriaParts.slice(1).join(".");
const alias = this.expressionMap.findAliasByName(aliasName);
if (alias.type !== "join") {
return "";
}
const column = alias.metadata.findColumnWithPropertyPath(propertyPath);
const property = this.escape(alias.name) + "." + this.escape(column!.databaseName);
const propertyAlias = this.escape(DriverUtils.buildAlias(this.connection.driver, aliasName, column!.databaseName));
return [property, "AS", propertyAlias].join(" ");
})
.join(", ");

return [selectString, orderByObject, subquerySelectString];
}

/**
Expand Down
16 changes: 16 additions & 0 deletions test/github-issues/3736/entity/Photo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "../../../../src";
import {User} from "./User";

@Entity()
export class Photo {

@PrimaryGeneratedColumn()
id: number;

@Column()
url: string;

@ManyToOne(() => User, user => user.photos)
user: User;

}
16 changes: 16 additions & 0 deletions test/github-issues/3736/entity/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "../../../../src";
import {Photo} from "./Photo";

@Entity()
export class User {

@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@OneToMany(() => Photo, photo => photo.user)
photos: Photo[];

}
40 changes: 40 additions & 0 deletions test/github-issues/3736/issue-3736.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import "reflect-metadata";
import { createTestingConnections, closeTestingConnections, reloadTestingDatabases } from "../../utils/test-utils";
import { Connection } from "../../../src/connection/Connection";
import { expect } from "chai";
import { Photo } from "./entity/Photo";
import { User } from "./entity/User";
import { SelectQueryBuilder } from "../../../src";


describe("github issues > #3736 Order by joined column broken in Postgres", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchema: true,
logging: true
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));

it("should return photos ordered by user.name", () => Promise.all(connections.map(async connection => {
const [user1, user2] = await connection.getRepository(User).save([
{ name: "userA" },
{ name: "userB" },
]);
const [photo1, photo2] = await connection.getRepository(Photo).save([
{ url: "https://example.com", user: user2 },
{ url: "https://example.com", user: user1 },
]);
const queryBuilder: SelectQueryBuilder<Photo> = await connection.getRepository(Photo).createQueryBuilder("photo");
const [results] = await queryBuilder
.select()
.leftJoin("photo.user", "user")
.take(5)
.orderBy("user.name")
.getManyAndCount();
expect(results[0].id).to.equal(photo2.id);
expect(results[1].id).to.equal(photo1.id);
})));
})