Skip to content

Commit

Permalink
fix(core): fix aliasing of queries with collection operators
Browse files Browse the repository at this point in the history
Closes #5301
  • Loading branch information
B4nan committed Mar 1, 2024
1 parent 169c692 commit 0435faf
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 16 deletions.
6 changes: 3 additions & 3 deletions packages/knex/src/query/ObjectCriteriaNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ export class ObjectCriteriaNode<T extends object> extends CriteriaNode<T> {
}

const payload = (this.payload[key] as CriteriaNode<T>).unwrap();
const sub = qb
.clone(true)
const qb2 = qb.clone(true);
const sub = qb2
.from(this.parent!.entityName)
.innerJoin(this.key!, qb.getNextAlias(this.prop!.type))
.innerJoin(this.key!, qb2.getNextAlias(this.prop!.type))
.select(this.prop!.targetMeta!.primaryKeys);

if (key === '$every') {
Expand Down
2 changes: 1 addition & 1 deletion packages/knex/src/query/QueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ export class QueryBuilder<T extends object = AnyEntity> {
const properties = [
'flags', '_populate', '_populateWhere', '__populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables', '_groupBy', '_having', '_returning',
'_comments', '_hintComments', 'rawFragments',
'_comments', '_hintComments', 'rawFragments', 'aliasCounter',
];

RawQueryFragment.cloneRegistry = this.rawFragments;
Expand Down
94 changes: 94 additions & 0 deletions tests/features/collection/GH5301.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Collection, Entity, ManyToMany, MikroORM, PrimaryKey, Property } from '@mikro-orm/sqlite';
import { mockLogger } from '../../helpers';

@Entity()
class UserLabel {

@PrimaryKey()
id!: number;

@Property()
name: string;

@ManyToMany(() => User, user => user.labels)
users = new Collection<User>(this);

constructor(id: number, name: string) {
this.id = id;
this.name = name;
}

}

@Entity()
class User {

@PrimaryKey()
id!: number;

@Property()
name: string;

@ManyToMany(() => UserLabel)
labels = new Collection<UserLabel>(this);

constructor(id: number, name: string) {
this.id = id;
this.name = name;
}

}

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
dbName: ':memory:',
entities: [User, UserLabel],
});
await orm.schema.createSchema();
});

afterAll(async () => {
await orm.close(true);
});

test('basic CRUD example', async () => {
const user1 = orm.em.create(User, {
id: 1,
name: 'User 1',
});

const user2 = orm.em.create(User, {
id: 2,
name: 'User 2',
});

orm.em.create(User, {
id: 3,
name: 'User 3',
});

const label1 = orm.em.create(UserLabel, {
id: 1,
name: 'Label 1',
});

const label2 = orm.em.create(UserLabel, {
id: 2,
name: 'Label 2',
});

user1.labels.add(label1, label2);
user2.labels.add(label1);

await orm.em.flush();
orm.em.clear();

const mock = mockLogger(orm);
const count = await orm.em.count(User, {
labels: { $some: { id: 1 } },
});
expect(count).toBe(2);
expect(mock.mock.calls[0][0]).toMatch('select count(*) as `count` from `user` as `u0` where `u0`.`id` in (select `u0`.`id` from `user` as `u0` left join `user_labels` as `u2` on `u0`.`id` = `u2`.`user_id` inner join `user_label` as `u1` on `u2`.`user_label_id` = `u1`.`id` where `u2`.`user_label_id` = 1)');
});
24 changes: 12 additions & 12 deletions tests/features/collection/collection-operators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ test('m:n sub-query operators $some, $none and $every (select-in)', async () =>
['t2'],
['t2'],
]);
expect(mock.mock.calls[0][0]).toBe("[query] select `b0`.* from `book` as `b0` where `b0`.`id` in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b1` on `b1`.`book_tag_id` = `b1`.`id` where `b1`.`name` in ('t1', 't2'))");
expect(mock.mock.calls[0][0]).toBe("[query] select `b0`.* from `book` as `b0` where `b0`.`id` in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where `b1`.`name` in ('t1', 't2'))");
expect(mock.mock.calls[1][0]).toBe('[query] select `b1`.*, `b0`.`book_tag_id` as `fk__book_tag_id`, `b0`.`book_id` as `fk__book_id` from `book_tags` as `b0` inner join `book_tag` as `b1` on `b0`.`book_tag_id` = `b1`.`id` where `b0`.`book_id` in (1, 2, 3, 4, 5, 6, 7, 8)');

results = await orm.em.fork().find(Book, {
Expand All @@ -168,7 +168,7 @@ test('m:n sub-query operators $some, $none and $every (select-in)', async () =>
expect(results.map(res => res.tags.getIdentifiers('name'))).toEqual([
['t4', 't5'],
]);
expect(mock.mock.calls[2][0]).toBe("[query] select `b0`.* from `book` as `b0` left join `book_tags` as `b3` on `b0`.`id` = `b3`.`book_id` left join `book_tag` as `b2` on `b3`.`book_tag_id` = `b2`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b1` on `b1`.`book_tag_id` = `b1`.`id` where `b1`.`name` in ('t1', 't2')) order by `b2`.`name` asc");
expect(mock.mock.calls[2][0]).toBe("[query] select `b0`.* from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where `b1`.`name` in ('t1', 't2')) order by `b1`.`name` asc");
expect(mock.mock.calls[3][0]).toBe('[query] select `b1`.*, `b0`.`book_tag_id` as `fk__book_tag_id`, `b0`.`book_id` as `fk__book_id` from `book_tags` as `b0` inner join `book_tag` as `b1` on `b0`.`book_tag_id` = `b1`.`id` where `b0`.`book_id` in (9) order by `b1`.`name` asc');

results = await orm.em.fork().find(Book, {
Expand All @@ -181,7 +181,7 @@ test('m:n sub-query operators $some, $none and $every (select-in)', async () =>
['t2'],
['t2'],
]);
expect(mock.mock.calls[4][0]).toBe("[query] select `b0`.* from `book` as `b0` left join `book_tags` as `b3` on `b0`.`id` = `b3`.`book_id` left join `book_tag` as `b2` on `b3`.`book_tag_id` = `b2`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b1` on `b1`.`book_tag_id` = `b1`.`id` where not (`b1`.`name` in ('t1', 't2'))) order by `b2`.`name` asc");
expect(mock.mock.calls[4][0]).toBe("[query] select `b0`.* from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where not (`b1`.`name` in ('t1', 't2'))) order by `b1`.`name` asc");
expect(mock.mock.calls[5][0]).toBe('[query] select `b1`.*, `b0`.`book_tag_id` as `fk__book_tag_id`, `b0`.`book_id` as `fk__book_id` from `book_tags` as `b0` inner join `book_tag` as `b1` on `b0`.`book_tag_id` = `b1`.`id` where `b0`.`book_id` in (3, 5, 6, 7, 8) order by `b1`.`name` asc');

results = await orm.em.fork().find(Book, {
Expand All @@ -198,14 +198,14 @@ test('m:n sub-query operators $some, $none and $every (select-in)', async () =>
['t2'],
['t4', 't5'],
]);
expect(mock.mock.calls[6][0]).toBe('[query] select `b0`.* from `book` as `b0` left join `book_tags` as `b3` on `b0`.`id` = `b3`.`book_id` left join `book_tag` as `b2` on `b3`.`book_tag_id` = `b2`.`id` where `b0`.`id` in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b1` on `b1`.`book_tag_id` = `b1`.`id`) order by `b2`.`name` asc');
expect(mock.mock.calls[6][0]).toBe('[query] select `b0`.* from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where `b0`.`id` in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id`) order by `b1`.`name` asc');
expect(mock.mock.calls[7][0]).toBe('[query] select `b1`.*, `b0`.`book_tag_id` as `fk__book_tag_id`, `b0`.`book_id` as `fk__book_id` from `book_tags` as `b0` inner join `book_tag` as `b1` on `b0`.`book_tag_id` = `b1`.`id` where `b0`.`book_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9) order by `b1`.`name` asc');

results = await orm.em.fork().find(Book, {
tags: { $none: {} },
}, { populate: ['tags'], orderBy: { tags: { name: 1 } } });
expect(results.map(res => res.tags.getIdentifiers('name'))).toEqual([]);
expect(mock.mock.calls[8][0]).toBe('[query] select `b0`.* from `book` as `b0` left join `book_tags` as `b3` on `b0`.`id` = `b3`.`book_id` left join `book_tag` as `b2` on `b3`.`book_tag_id` = `b2`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b1` on `b1`.`book_tag_id` = `b1`.`id`) order by `b2`.`name` asc');
expect(mock.mock.calls[8][0]).toBe('[query] select `b0`.* from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id`) order by `b1`.`name` asc');
});

test('m:n sub-query operators $some, $none and $every (joined)', async () => {
Expand All @@ -225,15 +225,15 @@ test('m:n sub-query operators $some, $none and $every (joined)', async () => {
['t2'],
['t2'],
]);
expect(mock.mock.calls[0][0]).toBe("[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b3` on `b1`.`book_tag_id` = `b3`.`id` where `b3`.`name` in ('t1', 't2'))");
expect(mock.mock.calls[0][0]).toBe("[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where `b1`.`name` in ('t1', 't2'))");

results = await orm.em.fork().find(Book, {
tags: { $none: { name: ['t1', 't2'] } },
}, { populate: ['tags'], orderBy: { tags: { name: 1 } } });
expect(results.map(res => res.tags.getIdentifiers('name'))).toEqual([
['t4', 't5'],
]);
expect(mock.mock.calls[1][0]).toBe("[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b3` on `b1`.`book_tag_id` = `b3`.`id` where `b3`.`name` in ('t1', 't2')) order by `t1`.`name` asc");
expect(mock.mock.calls[1][0]).toBe("[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where `b1`.`name` in ('t1', 't2')) order by `t1`.`name` asc");

results = await orm.em.fork().find(Book, {
tags: { $every: { name: ['t1', 't2'] } },
Expand All @@ -245,7 +245,7 @@ test('m:n sub-query operators $some, $none and $every (joined)', async () => {
['t2'],
['t2'],
]);
expect(mock.mock.calls[2][0]).toBe("[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b3` on `b1`.`book_tag_id` = `b3`.`id` where not (`b3`.`name` in ('t1', 't2'))) order by `t1`.`name` asc");
expect(mock.mock.calls[2][0]).toBe("[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id` where not (`b1`.`name` in ('t1', 't2'))) order by `t1`.`name` asc");

results = await orm.em.fork().find(Book, {
tags: { $some: {} },
Expand All @@ -261,13 +261,13 @@ test('m:n sub-query operators $some, $none and $every (joined)', async () => {
['t2'],
['t4', 't5'],
]);
expect(mock.mock.calls[3][0]).toBe('[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b3` on `b1`.`book_tag_id` = `b3`.`id`) order by `t1`.`name` asc');
expect(mock.mock.calls[3][0]).toBe('[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id`) order by `t1`.`name` asc');

results = await orm.em.fork().find(Book, {
tags: { $none: {} },
}, { populate: ['tags'], orderBy: { tags: { name: 1 } } });
expect(results.map(res => res.tags.getIdentifiers('name'))).toEqual([]);
expect(mock.mock.calls[4][0]).toBe('[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b1` on `b0`.`id` = `b1`.`book_id` inner join `book_tag` as `b3` on `b1`.`book_tag_id` = `b3`.`id`) order by `t1`.`name` asc');
expect(mock.mock.calls[4][0]).toBe('[query] select `b0`.*, `t1`.`id` as `t1__id`, `t1`.`name` as `t1__name` from `book` as `b0` left join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` left join `book_tag` as `t1` on `b2`.`book_tag_id` = `t1`.`id` where `b0`.`id` not in (select `b0`.`id` from `book` as `b0` inner join `book_tags` as `b2` on `b0`.`id` = `b2`.`book_id` inner join `book_tag` as `b1` on `b2`.`book_tag_id` = `b1`.`id`) order by `t1`.`name` asc');
});

test('allows only one of $some, $none and $every on the given level', async () => {
Expand All @@ -278,7 +278,7 @@ test('allows only one of $some, $none and $every on the given level', async () =
$none: { title: 'Foo' },
},
});
expect(mock.mock.calls[0][0]).toBe("[query] select `a0`.* from `author` as `a0` where `a0`.`id` in (select `a0`.`id` from `author` as `a0` inner join `book` as `b1` on `a0`.`id` = `b1`.`author_id` where `b1`.`title` = 'Foo') and `a0`.`id` not in (select `a0`.`id` from `author` as `a0` inner join `book` as `b2` on `a0`.`id` = `b2`.`author_id` where `b2`.`title` = 'Foo')");
expect(mock.mock.calls[0][0]).toBe("[query] select `a0`.* from `author` as `a0` where `a0`.`id` in (select `a0`.`id` from `author` as `a0` inner join `book` as `b1` on `a0`.`id` = `b1`.`author_id` where `b1`.`title` = 'Foo') and `a0`.`id` not in (select `a0`.`id` from `author` as `a0` inner join `book` as `b1` on `a0`.`id` = `b1`.`author_id` where `b1`.`title` = 'Foo')");
expect(results).toHaveLength(0);

results = await orm.em.fork().find(Author, {
Expand All @@ -287,7 +287,7 @@ test('allows only one of $some, $none and $every on the given level', async () =
$none: { title: 'Foo 123' },
},
});
expect(mock.mock.calls[1][0]).toBe("[query] select `a0`.* from `author` as `a0` where `a0`.`id` in (select `a0`.`id` from `author` as `a0` inner join `book` as `b1` on `a0`.`id` = `b1`.`author_id` where `b1`.`title` = 'Foo') and `a0`.`id` not in (select `a0`.`id` from `author` as `a0` inner join `book` as `b2` on `a0`.`id` = `b2`.`author_id` where `b2`.`title` = 'Foo 123')");
expect(mock.mock.calls[1][0]).toBe("[query] select `a0`.* from `author` as `a0` where `a0`.`id` in (select `a0`.`id` from `author` as `a0` inner join `book` as `b1` on `a0`.`id` = `b1`.`author_id` where `b1`.`title` = 'Foo') and `a0`.`id` not in (select `a0`.`id` from `author` as `a0` inner join `book` as `b1` on `a0`.`id` = `b1`.`author_id` where `b1`.`title` = 'Foo 123')");
expect(results.map(res => res.name)).toEqual([
'Author 2',
'Author 3',
Expand Down

0 comments on commit 0435faf

Please sign in to comment.