UNPKG

undeexcepturi

Version:

TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.

405 lines (360 loc) 18.8 kB
import type { MikroORM } from '@mikro-orm/core'; import { LoadStrategy } from '@mikro-orm/core'; import { MySqlDriver } from '@mikro-orm/mysql'; import { Author2, Book2, BookTag2 } from '../../entities-sql'; import { initORMMySql, mockLogger } from '../../bootstrap'; describe('partial loading (mysql)', () => { let orm: MikroORM<MySqlDriver>; beforeAll(async () => orm = await initORMMySql('mysql', { dbName: 'partial_loading' }, true)); beforeEach(async () => orm.schema.clearDatabase()); afterAll(async () => await orm.close(true)); async function createEntities() { const god = new Author2(`God `, `hello@heaven.god`); const b1 = orm.em.create(Book2, { title: `Bible 1`, author: god }); b1.price = 123; b1.tags.add(new BookTag2('t1'), new BookTag2('t2')); const b2 = orm.em.create(Book2, { title: `Bible 2`, author: god }); b2.price = 456; b2.tags.add(new BookTag2('t3'), new BookTag2('t4')); const b3 = orm.em.create(Book2, { title: `Bible 3`, author: god }); b3.price = 789; b3.tags.add(new BookTag2('t5'), new BookTag2('t6')); await orm.em.persistAndFlush(god); orm.em.clear(); return god; } test('partial selects', async () => { const author = new Author2('Jon Snow', 'snow@wall.st'); author.born = '1990-03-23'; await orm.em.persistAndFlush(author); orm.em.clear(); const a = (await orm.em.findOne(Author2, author, { fields: ['name'] }))!; expect(a.name).toBe('Jon Snow'); // @ts-expect-error expect(a.email).toBeUndefined(); // @ts-expect-error expect(a.born).toBeUndefined(); const a1 = orm.em.assign(a, { email: 'e1' }); expect(a1.name).toBe('Jon Snow'); expect(a1.email).toBe('e1'); // @ts-expect-error expect(a1.termsAccepted).toBeUndefined(); const a2 = orm.em.repo(Author2).assign(a, { email: 'e1' }); expect(a2.name).toBe('Jon Snow'); expect(a2.email).toBe('e1'); // @ts-expect-error expect(a2.termsAccepted).toBeUndefined(); const a3 = orm.em.assign(a1, { born: '1990-03-24' }); expect(a3.name).toBe('Jon Snow'); expect(a3.email).toBe('e1'); expect(a3.born).toBe('1990-03-24'); // @ts-expect-error expect(a3.termsAccepted).toBeUndefined(); // @ts-expect-error const a4 = orm.em.repo(Author2).assign(a2, { born: '1990-03-24', asd: true }); expect(a4.name).toBe('Jon Snow'); expect(a4.email).toBe('e1'); expect(a4.born).toBe('1990-03-24'); // @ts-expect-error expect(a4.asd).toBe(true); await orm.em.flush(); orm.em.clear(); const a5 = (await orm.em.findOne(Author2, author, { fields: ['*'] }))!; expect(a5.name).toBe('Jon Snow'); expect(a5.email).toBe('e1'); expect(a5.born).toEqual('1990-03-24'); }); test('partial nested loading (1:m)', async () => { const god = await createEntities(); const rr = await orm.em.findOneOrFail(Author2, god, { fields: ['name', 'favouriteBook.title', 'favouriteBook.double', 'books.publisher.name'], disableIdentityMap: true, }); // @ts-expect-error rr.favouriteBook?.author; rr.favouriteBook?.title; rr.favouriteBook?.double; rr.favouriteBook?. // @ts-expect-error publisher?.$.name; // @ts-expect-error rr.books.$[0].title; rr.books.$[0].publisher?.$.name; // @ts-expect-error rr.books.$[0].publisher?.$.type; // test working with scalars expect(`This is User #${rr.id.toFixed()} with name '${rr.name.substring(0, 3)}'`).toBe(`This is User #1 with name 'God'`); const mock = mockLogger(orm, ['query']); const r1 = await orm.em.find(Author2, god, { fields: ['id', 'books.author', 'books.title'] }); expect(r1).toHaveLength(1); expect(r1[0].id).toBe(god.id); // @ts-expect-error expect(r1[0].name).toBeUndefined(); expect(r1[0].books[0].uuid).toBe(god.books[0].uuid); expect(r1[0].books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r1[0].books[0].price).toBeUndefined(); expect(r1[0].books[0].author).toBeDefined(); expect(mock.mock.calls[0][0]).toMatch('select `a0`.`id`, `b1`.`uuid_pk` as `b1__uuid_pk`, `b1`.`title` as `b1__title`, `b1`.`author_id` as `b1__author_id` from `author2` as `a0` left join `book2` as `b1` on `a0`.`id` = `b1`.`author_id` and `b1`.`author_id` is not null where `a0`.`id` = ? order by `b1`.`title` asc'); orm.em.clear(); mock.mock.calls.length = 0; // old syntax is still supported but will yield Loaded instead of Selected const r2 = await orm.em.find(Author2, god, { fields: ['id', { books: ['uuid', 'author', 'title'] } as any] }); expect(r2).toHaveLength(1); expect(r2[0].id).toBe(god.id); expect(r2[0].name).toBeUndefined(); expect(r2[0].books[0].uuid).toBe(god.books[0].uuid); expect(r2[0].books[0].title).toBe('Bible 1'); expect(r2[0].books[0].price).toBeUndefined(); expect(r2[0].books[0].author).toBeDefined(); expect(mock.mock.calls[0][0]).toMatch('select `a0`.`id`, `b1`.`uuid_pk` as `b1__uuid_pk`, `b1`.`title` as `b1__title`, `b1`.`author_id` as `b1__author_id` from `author2` as `a0` left join `book2` as `b1` on `a0`.`id` = `b1`.`author_id` and `b1`.`author_id` is not null where `a0`.`id` = ? order by `b1`.`title` asc'); orm.em.clear(); mock.mock.calls.length = 0; // collection properties in `fields` are ignored const r0 = await orm.em.find(Author2, god, { fields: ['id', 'books', 'books.author', 'books.title'] }); expect(r0).toHaveLength(1); expect(r0[0].id).toBe(god.id); // @ts-expect-error expect(r0[0].name).toBeUndefined(); expect(r0[0].books[0].uuid).toBe(god.books[0].uuid); expect(r0[0].books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r0[0].books[0].price).toBeUndefined(); expect(r0[0].books[0].author).toBeDefined(); expect(mock.mock.calls[0][0]).toMatch('select `a0`.`id`, `b1`.`uuid_pk` as `b1__uuid_pk`, `b1`.`title` as `b1__title`, `b1`.`author_id` as `b1__author_id` from `author2` as `a0` left join `book2` as `b1` on `a0`.`id` = `b1`.`author_id` and `b1`.`author_id` is not null where `a0`.`id` = ? order by `b1`.`title` asc'); orm.em.clear(); mock.mock.calls.length = 0; // partial loading with query builder const r3 = await orm.em.qb(Author2, 'a') .select('id') .innerJoinAndSelect('a.books', 'b', {}, ['author', 'title']) .where({ id: god.id }) .orderBy({ 'b.title': 1 }); expect(r3).toHaveLength(1); expect(r3[0].id).toBe(god.id); expect(r3[0].name).toBeUndefined(); expect(r3[0].books[0].uuid).toBe(god.books[0].uuid); expect(r3[0].books[0].title).toBe('Bible 1'); expect(r3[0].books[0].price).toBeUndefined(); expect(r3[0].books[0].author).toBeDefined(); expect(mock.mock.calls[0][0]).toMatch('select `a`.`id`, `b`.`uuid_pk` as `b__uuid_pk`, `b`.`title` as `b__title`, `b`.`author_id` as `b__author_id` from `author2` as `a` inner join `book2` as `b` on `a`.`id` = `b`.`author_id` where `a`.`id` = ? order by `b`.`title` asc'); orm.em.clear(); mock.mock.calls.length = 0; // when populating collections, the owner is selected automatically (here book.author) const r00 = await orm.em.find(Author2, god, { fields: ['id', 'books.title'] }); expect(r00).toHaveLength(1); expect(r00[0].id).toBe(god.id); // @ts-expect-error expect(r00[0].name).toBeUndefined(); expect(r00[0].books[0].uuid).toBe(god.books[0].uuid); expect(r00[0].books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r00[0].books[0].price).toBeUndefined(); // @ts-expect-error expect(r00[0].books[0].author).toBeDefined(); expect(mock.mock.calls[0][0]).toMatch('select `a0`.`id`, `b1`.`uuid_pk` as `b1__uuid_pk`, `b1`.`title` as `b1__title` from `author2` as `a0` left join `book2` as `b1` on `a0`.`id` = `b1`.`author_id` and `b1`.`author_id` is not null where `a0`.`id` = ? order by `b1`.`title` asc'); }); test('partial nested loading (m:1)', async () => { const god = await createEntities(); const b1 = god.books[0]; const mock = mockLogger(orm, ['query']); const r1 = await orm.em.find(Book2, b1, { fields: ['uuid', 'title', 'author.email'], populate: ['author'], filters: false, strategy: LoadStrategy.SELECT_IN, }); expect(r1).toHaveLength(1); expect(r1[0].uuid).toBe(b1.uuid); expect(r1[0].title).toBe('Bible 1'); // @ts-expect-error expect(r1[0].price).toBeUndefined(); expect(r1[0].author).toBeDefined(); expect(r1[0].author.id).toBe(god.id); // @ts-expect-error expect(r1[0].author.name).toBeUndefined(); expect(r1[0].author.email).toBeDefined(); expect(mock.mock.calls[0][0]).toMatch('select `b0`.`uuid_pk`, `b0`.`title`, `b0`.`author_id` from `book2` as `b0` where `b0`.`uuid_pk` = ?'); expect(mock.mock.calls[1][0]).toMatch('select `a0`.`id`, `a0`.`email` from `author2` as `a0` where `a0`.`id` in (?)'); orm.em.clear(); mock.mock.calls.length = 0; const r2 = await orm.em.find(Book2, b1, { fields: ['uuid', 'title', 'author.email'], populate: ['author'], filters: false, strategy: LoadStrategy.JOINED, }); expect(r2).toHaveLength(1); expect(r2[0].uuid).toBe(b1.uuid); expect(r2[0].title).toBe('Bible 1'); // @ts-expect-error expect(r2[0].price).toBeUndefined(); expect(r2[0].author).toBeDefined(); expect(r2[0].author.id).toBe(god.id); // @ts-expect-error expect(r2[0].author.name).toBeUndefined(); expect(r2[0].author.email).toBeDefined(); expect(mock.mock.calls[0][0]).toMatch('select `b0`.`uuid_pk`, `b0`.`title`, `b0`.`author_id`, `a1`.`id` as `a1__id`, `a1`.`email` as `a1__email` from `book2` as `b0` left join `author2` as `a1` on `b0`.`author_id` = `a1`.`id` where `b0`.`uuid_pk` = ?'); }); test('partial nested loading (m:n)', async () => { await createEntities(); const mock = mockLogger(orm, ['query']); const r1 = await orm.em.find(BookTag2, {}, { fields: ['name', 'books.title'], filters: false }); expect(r1).toHaveLength(6); expect(r1[0].name).toBe('t1'); expect(r1[0].books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r1[0].books[0].price).toBeUndefined(); // @ts-expect-error expect(r1[0].books[0].author).toBeUndefined(); expect(mock.mock.calls[0][0]).toMatch('select `b0`.`id`, `b0`.`name`, `b1`.`uuid_pk` as `b1__uuid_pk`, `b1`.`title` as `b1__title` from `book_tag2` as `b0` left join `book2_tags` as `b2` on `b0`.`id` = `b2`.`book_tag2_id` left join `book2` as `b1` on `b2`.`book2_uuid_pk` = `b1`.`uuid_pk` order by `b2`.`order` asc'); orm.em.clear(); mock.mock.calls.length = 0; const r2 = await orm.em.find(BookTag2, { name: 't1' }, { fields: ['name', 'books.title'], filters: false }); expect(r2).toHaveLength(1); expect(r2[0].name).toBe('t1'); expect(r2[0].books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r2[0].books[0].price).toBeUndefined(); // @ts-expect-error expect(r2[0].books[0].author).toBeUndefined(); expect(mock.mock.calls[0][0]).toMatch('select `b0`.`id`, `b0`.`name`, `b1`.`uuid_pk` as `b1__uuid_pk`, `b1`.`title` as `b1__title` from `book_tag2` as `b0` left join `book2_tags` as `b2` on `b0`.`id` = `b2`.`book_tag2_id` left join `book2` as `b1` on `b2`.`book2_uuid_pk` = `b1`.`uuid_pk` where `b0`.`name` = ? order by `b2`.`order` asc'); }); test('partial nested loading (dot notation, select-in)', async () => { const god = await createEntities(); const mock = mockLogger(orm, ['query']); const r1 = await orm.em.find(BookTag2, {}, { fields: ['name', 'books.title', 'books.author', 'books.author.email'], populate: ['books.author'], filters: false, strategy: LoadStrategy.SELECT_IN, }); expect(r1).toHaveLength(6); expect(r1[0].name).toBe('t1'); expect(r1[0].books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r1[0].books[0].price).toBeUndefined(); expect(r1[0].books[0].author).toBeDefined(); expect(r1[0].books[0].author.id).toBeDefined(); // @ts-expect-error expect(r1[0].books[0].author.name).toBeUndefined(); expect(r1[0].books[0].author.email).toBe(god.email); expect(mock.mock.calls[0][0]).toMatch('select `b0`.`id`, `b0`.`name` from `book_tag2` as `b0`'); expect(mock.mock.calls[1][0]).toMatch('select `b1`.`author_id`, `b1`.`title`, `b1`.`uuid_pk`, `b0`.`book_tag2_id` as `fk__book_tag2_id`, `b0`.`book2_uuid_pk` as `fk__book2_uuid_pk`, `b2`.`id` as `test_id` from `book2_tags` as `b0` inner join `book2` as `b1` on `b0`.`book2_uuid_pk` = `b1`.`uuid_pk` left join `test2` as `b2` on `b1`.`uuid_pk` = `b2`.`book_uuid_pk` where `b0`.`book_tag2_id` in (?, ?, ?, ?, ?, ?) order by `b0`.`order` asc'); expect(mock.mock.calls[2][0]).toMatch('select `a0`.`id`, `a0`.`email` from `author2` as `a0` where `a0`.`id` in (?)'); }); test('partial nested loading (with joined strategy and dot notation)', async () => { const god = await createEntities(); const mock = mockLogger(orm, ['query']); const r3 = await orm.em.find(BookTag2, {}, { fields: ['name', 'books.title', 'books.author', 'books.author.email'], populate: ['books.author'], filters: false, }); expect(r3).toHaveLength(6); expect(r3[0].name).toBe('t1'); expect(r3[0].books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r3[0].books[0].price).toBeUndefined(); expect(r3[0].books[0].author).toBeDefined(); expect(r3[0].books[0].author.id).toBeDefined(); // @ts-expect-error expect(r3[0].books[0].author.name).toBeUndefined(); expect(r3[0].books[0].author.email).toBe(god.email); expect(mock.mock.calls).toHaveLength(1); expect(mock.mock.calls[0][0]).toMatch('select `b0`.`id`, `b0`.`name`, ' + '`b1`.`uuid_pk` as `b1__uuid_pk`, `b1`.`title` as `b1__title`, `b1`.`author_id` as `b1__author_id`, ' + '`a3`.`id` as `a3__id`, `a3`.`email` as `a3__email` ' + 'from `book_tag2` as `b0` ' + 'left join `book2_tags` as `b2` on `b0`.`id` = `b2`.`book_tag2_id` ' + 'left join `book2` as `b1` on `b2`.`book2_uuid_pk` = `b1`.`uuid_pk` ' + 'left join `author2` as `a3` on `b1`.`author_id` = `a3`.`id`'); mock.mockReset(); const r2 = await orm.em.find(BookTag2, {}, { fields: ['*', 'books.title', 'books.author', 'books.author.email'], populate: ['books.author'], filters: false, }); expect(r2).toHaveLength(6); expect(mock.mock.calls).toHaveLength(1); expect(mock.mock.calls[0][0]).toMatch('select `b0`.*, ' + '`b1`.`uuid_pk` as `b1__uuid_pk`, `b1`.`title` as `b1__title`, `b1`.`author_id` as `b1__author_id`, ' + '`a3`.`id` as `a3__id`, `a3`.`email` as `a3__email` ' + 'from `book_tag2` as `b0` ' + 'left join `book2_tags` as `b2` on `b0`.`id` = `b2`.`book_tag2_id` ' + 'left join `book2` as `b1` on `b2`.`book2_uuid_pk` = `b1`.`uuid_pk` ' + 'left join `author2` as `a3` on `b1`.`author_id` = `a3`.`id` ' + 'order by `b2`.`order` asc'); }); test('partial nested loading (object notation, select-in)', async () => { const god = await createEntities(); const mock = mockLogger(orm, ['query']); const r2 = await orm.em.find(BookTag2, {}, { fields: ['name', 'books.title', 'books.author', 'books.author.email'], filters: false, strategy: LoadStrategy.SELECT_IN, }); expect(r2).toHaveLength(6); expect(r2[0].name).toBe('t1'); expect(r2[0].books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r2[0].books[0].price).toBeUndefined(); expect(r2[0].books[0].author).toBeDefined(); expect(r2[0].books[0].author.id).toBeDefined(); // @ts-expect-error expect(r2[0].books[0].author.name).toBeUndefined(); expect(r2[0].books[0].author.email).toBe(god.email); expect(mock.mock.calls[0][0]).toMatch('select `b0`.`id`, `b0`.`name` from `book_tag2` as `b0`'); expect(mock.mock.calls[1][0]).toMatch('select `b1`.`author_id`, `b1`.`title`, `b1`.`uuid_pk`, `b0`.`book_tag2_id` as `fk__book_tag2_id`, `b0`.`book2_uuid_pk` as `fk__book2_uuid_pk`, `b2`.`id` as `test_id` from `book2_tags` as `b0` inner join `book2` as `b1` on `b0`.`book2_uuid_pk` = `b1`.`uuid_pk` left join `test2` as `b2` on `b1`.`uuid_pk` = `b2`.`book_uuid_pk` where `b0`.`book_tag2_id` in (?, ?, ?, ?, ?, ?) order by `b0`.`order` asc'); expect(mock.mock.calls[2][0]).toMatch('select `a0`.`id`, `a0`.`email` from `author2` as `a0` where `a0`.`id` in (?)'); }); test('partial nested loading (with joined strategy)', async () => { const god = await createEntities(); const mock = mockLogger(orm, ['query']); const r3 = await orm.em.find(BookTag2, {}, { fields: ['name', 'books.title', 'books.author.email'], filters: false, }); expect(r3).toHaveLength(6); expect(r3[0].name).toBe('t1'); expect(r3[0].books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r3[0].books[0].price).toBeUndefined(); expect(r3[0].books[0].author).toBeDefined(); expect(r3[0].books[0].author.id).toBeDefined(); // @ts-expect-error expect(r3[0].books[0].author.name).toBeUndefined(); expect(r3[0].books[0].author.email).toBe(god.email); expect(mock.mock.calls).toHaveLength(1); expect(mock.mock.calls[0][0]).toMatch('select `b0`.`id`, `b0`.`name`, ' + '`b1`.`uuid_pk` as `b1__uuid_pk`, `b1`.`title` as `b1__title`, `b1`.`author_id` as `b1__author_id`, ' + '`a3`.`id` as `a3__id`, `a3`.`email` as `a3__email` ' + 'from `book_tag2` as `b0` ' + 'left join `book2_tags` as `b2` on `b0`.`id` = `b2`.`book_tag2_id` ' + 'left join `book2` as `b1` on `b2`.`book2_uuid_pk` = `b1`.`uuid_pk` ' + 'left join `author2` as `a3` on `b1`.`author_id` = `a3`.`id`'); }); test('populate partial', async () => { const god = await createEntities(); const r1 = await orm.em.findOneOrFail(BookTag2, 1, { fields: ['name', 'books.title', 'books.author.email'], }); expect(r1.name).toBe('t1'); expect(r1.books[0].title).toBe('Bible 1'); // @ts-expect-error expect(r1.books[0].price).toBeUndefined(); expect(r1.books[0].author).toBeDefined(); expect(r1.books[0].author.id).toBeDefined(); // @ts-expect-error expect(r1.books[0].author.name).toBeUndefined(); expect(r1.books[0].author.email).toBe(god.email); const r2 = await orm.em.refreshOrFail(r1, { populate: ['*'] }); expect(r2.name).toBe('t1'); expect(r2.books[0].title).toBe('Bible 1'); expect(r2.books[0].price).toBe(123.00); expect(r2.books[0].author).toBeDefined(); expect(r2.books[0].author.id).toBeDefined(); expect(r2.books[0].author.name).toBe(god.name); expect(r2.books[0].author.email).toBe(god.email); }); });