Skip to content

Commit

Permalink
fix(core): respect raw fragments in orderBy and populateOrderBy
Browse files Browse the repository at this point in the history
Closes #5110
  • Loading branch information
B4nan committed Jan 10, 2024
1 parent 6198a6c commit 7bf986c
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 6 deletions.
17 changes: 14 additions & 3 deletions packages/knex/src/AbstractSqlDriver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Knex } from 'knex';
import {
ALIAS_REPLACEMENT_RE,
type AnyEntity,
type Collection,
type Configuration,
Expand Down Expand Up @@ -45,6 +46,7 @@ import {
type QueryOrderMap,
type QueryResult,
raw,
RawQueryFragment,
ReferenceKind,
type RequiredEntityData,
type Transaction,
Expand Down Expand Up @@ -1255,19 +1257,28 @@ export abstract class AbstractSqlDriver<Connection extends AbstractSqlConnection
// as `options.populateWhere` will be always recomputed to respect filters
const populateWhereAll = (options as Dictionary)._populateWhere !== 'infer' && !Utils.isEmpty((options as Dictionary)._populateWhere);
const path = (populateWhereAll ? '[populate]' : '') + meta.className;
const populateOrderBy = this.buildPopulateOrderBy(qb, meta, Utils.asArray<QueryOrderMap<T>>(options.populateOrderBy ?? options.orderBy), path);
const populateOrderBy = this.buildPopulateOrderBy(qb, meta, Utils.asArray<QueryOrderMap<T>>(options.populateOrderBy ?? options.orderBy), path, !!options.populateOrderBy);
const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta, joinedProps, options, path);

return [...Utils.asArray(options.orderBy), ...populateOrderBy, ...joinedPropsOrderBy] as QueryOrderMap<T>[];
}

protected buildPopulateOrderBy<T extends object>(qb: QueryBuilder<T>, meta: EntityMetadata<T>, populateOrderBy: QueryOrderMap<T>[], parentPath: string, parentAlias = qb.alias): QueryOrderMap<T>[] {
protected buildPopulateOrderBy<T extends object>(qb: QueryBuilder<T>, meta: EntityMetadata<T>, populateOrderBy: QueryOrderMap<T>[], parentPath: string, explicit: boolean, parentAlias = qb.alias): QueryOrderMap<T>[] {
const orderBy: QueryOrderMap<T>[] = [];

for (let i = 0; i < populateOrderBy.length; i++) {
const orderHint = populateOrderBy[i];

for (const propName of Utils.keys(orderHint)) {
const raw = RawQueryFragment.getKnownFragment(propName, explicit);

if (raw) {
const sql = raw.sql.replace(new RegExp(ALIAS_REPLACEMENT_RE, 'g'), parentAlias);
const raw2 = new RawQueryFragment(sql, raw.params);
orderBy.push({ [raw2 as EntityKey]: orderHint[propName] } as QueryOrderMap<T>);
continue;
}

const prop = meta.properties[propName];

if (!prop) {
Expand All @@ -1290,7 +1301,7 @@ export abstract class AbstractSqlDriver<Connection extends AbstractSqlConnection
}

if (![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
const children = this.buildPopulateOrderBy(qb, meta2, Utils.asArray(childOrder as QueryOrderMap<T>), path, propAlias);
const children = this.buildPopulateOrderBy(qb, meta2, Utils.asArray(childOrder as QueryOrderMap<T>), path, explicit, propAlias);
orderBy.push(...children);
continue;
}
Expand Down
67 changes: 64 additions & 3 deletions tests/issues/GHx6.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Entity, MikroORM, PrimaryKey, Property, raw } from '@mikro-orm/sqlite';
import { Entity, ManyToOne, MikroORM, PrimaryKey, Property, raw } from '@mikro-orm/sqlite';
import { mockLogger } from '../helpers';

@Entity()
class Job {
Expand All @@ -11,11 +12,28 @@ class Job {

}

@Entity()
class Tag {

@PrimaryKey()
id!: number;

@Property()
name!: string;

@Property({ nullable: true })
created?: Date | null;

@ManyToOne(() => Job, { name: 'custom_name' })
job!: Job;

}

let orm: MikroORM;

beforeAll(async () => {
orm = MikroORM.initSync({
entities: [Job],
entities: [Job, Tag],
dbName: `:memory:`,
});

Expand All @@ -26,9 +44,52 @@ afterAll(async () => {
await orm.close(true);
});

it('raw fragments with findAndCount', async () => {
test('raw fragments with findAndCount', async () => {
await orm.em.findAndCount(Job, {
dateCompleted: { $ne: null },
[raw(alias => `${alias}.DateCompleted`)]: '2023-07-24',
});
});

test('raw fragments with orderBy', async () => {
const mock = mockLogger(orm);
await orm.em.findAll(Job, {
orderBy: {
[raw(alias => `${alias}.DateCompleted`)]: 'desc',
},
});
expect(mock.mock.calls[0][0]).toMatch('select `j0`.* from `job` as `j0` order by j0.DateCompleted desc');
});

test('raw fragments with orderBy on relation', async () => {
const mock = mockLogger(orm);
await orm.em.findAll(Tag, {
populate: ['job'],
orderBy: {
job: {
[raw(alias => `${alias}.DateCompleted`)]: 'desc',
},
},
});
expect(mock.mock.calls[0][0]).toMatch('select `t0`.*, `j1`.`id` as `j1__id`, `j1`.`DateCompleted` as `j1__DateCompleted` ' +
'from `tag` as `t0` ' +
'left join `job` as `j1` on `t0`.`custom_name` = `j1`.`id` ' +
'order by j1.DateCompleted desc');
});

test('raw fragments with populateOrderBy on relation', async () => {
const mock = mockLogger(orm);
await orm.em.findAll(Tag, {
populate: ['job'],
populateOrderBy: {
[raw(alias => `${alias}.created`)]: 'desc',
job: {
[raw(alias => `${alias}.DateCompleted`)]: 'desc',
},
},
});
expect(mock.mock.calls[0][0]).toMatch('select `t0`.*, `j1`.`id` as `j1__id`, `j1`.`DateCompleted` as `j1__DateCompleted` ' +
'from `tag` as `t0` ' +
'left join `job` as `j1` on `t0`.`custom_name` = `j1`.`id` ' +
'order by t0.created desc, j1.DateCompleted desc');
});

0 comments on commit 7bf986c

Please sign in to comment.