Skip to content

Commit

Permalink
perf(query-builder): remove unnecessary join branches when pagination…
Browse files Browse the repository at this point in the history
… is applied
  • Loading branch information
B4nan committed Apr 6, 2024
1 parent 8f32a05 commit d228976
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/knex/src/dialects/mssql/MsSqlTableCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class MsSqlTableCompiler extends MonkeyPatchable.MsSqlTableCompiler {
}

dropForeign(this: any, columns: any, constraintName: any) {
/* istanbul ignore next */
constraintName = constraintName
? this.formatter.wrap(constraintName)
: this._indexCommand('foreign', this.tableNameRaw, columns);
Expand Down
30 changes: 29 additions & 1 deletion packages/knex/src/query/QueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1229,7 +1229,7 @@ export class QueryBuilder<T extends object = AnyEntity> {
}

for (const f of Object.keys(this._populateMap)) {
if (type === 'where') {
if (type === 'where' && this._joins[f]) {
const cols = this.helper.mapJoinColumns(this.type ?? QueryType.SELECT, this._joins[f]);

for (const col of cols) {
Expand Down Expand Up @@ -1548,6 +1548,34 @@ export class QueryBuilder<T extends object = AnyEntity> {
(subSubQuery as Dictionary).__raw = true; // tag it as there is now way to check via `instanceof`
this._limit = undefined;
this._offset = undefined;

// remove joins that are not used for population or ordering to improve performance
const populate = new Set<string>();
const orderByAliases = this._orderBy
.flatMap(hint => Object.keys(hint))
.map(k => k.split('.')[0]);

function addPath(hints: PopulateOptions<any>[], prefix = '') {
for (const hint of hints) {
const field = hint.field.split(':')[0];
populate.add((prefix ? prefix + '.' : '') + field);

if (hint.children) {
addPath(hint.children, (prefix ? prefix + '.' : '') + field);
}
}
}

addPath(this._populate);

for (const [key, join] of Object.entries(this._joins)) {
const path = join.path?.replace(/\[populate]|\[pivot]|:ref/g, '').replace(new RegExp(`^${meta.className}.`), '');

if (!populate.has(path ?? '') && !orderByAliases.includes(join.alias)) {
delete this._joins[key];
}
}

this.select(this._fields!).where({ [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery } });
}

Expand Down
2 changes: 1 addition & 1 deletion tests/QueryBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ describe('QueryBuilder', () => {
.orderBy({ booksTotal: QueryOrder.ASC });

await qb;
expect(qb.getQuery()).toEqual('select `a`.*, (select count(distinct `b`.`uuid_pk`) as `count` from `book2` as `b` where `b`.`author_id` = `a`.`id`) as `books_total` from `author2` as `a` left join `book2` as `e1` on `a`.`id` = `e1`.`author_id` where `a`.`id` in (select `a`.`id` from (select `a`.`id`, (select count(distinct `b`.`uuid_pk`) as `count` from `book2` as `b` where `b`.`author_id` = `a`.`id`) as `books_total` from `author2` as `a` left join `book2` as `e1` on `a`.`id` = `e1`.`author_id` where `e1`.`title` = ? group by `a`.`id` order by min(`books_total`) asc limit ?) as `a`) order by `books_total` asc');
expect(qb.getQuery()).toEqual('select `a`.*, (select count(distinct `b`.`uuid_pk`) as `count` from `book2` as `b` where `b`.`author_id` = `a`.`id`) as `books_total` from `author2` as `a` where `a`.`id` in (select `a`.`id` from (select `a`.`id`, (select count(distinct `b`.`uuid_pk`) as `count` from `book2` as `b` where `b`.`author_id` = `a`.`id`) as `books_total` from `author2` as `a` left join `book2` as `e1` on `a`.`id` = `e1`.`author_id` where `e1`.`title` = ? group by `a`.`id` order by min(`books_total`) asc limit ?) as `a`) order by `books_total` asc');
expect(qb.getParams()).toEqual(['foo', 1]);
});

Expand Down

0 comments on commit d228976

Please sign in to comment.