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.
785 lines (641 loc) • 27 kB
text/typescript
import {
MikroORM,
Entity,
PrimaryKey,
ManyToOne,
Property,
SimpleLogger,
Unique,
Ref,
ref,
EventSubscriber,
EventArgs,
OneToMany,
Collection,
Embeddable,
Embedded,
OptionalProps,
Utils,
IDatabaseDriver,
AfterUpsert,
BeforeUpsert,
} from '@mikro-orm/core';
import { mockLogger } from '../../helpers';
import { PLATFORMS } from '../../bootstrap';
@Entity()
export class Author {
[OptionalProps]?: 'foo';
static id = 1;
static hooks = [] as string[];
@PrimaryKey({ name: '_id' })
id: number = Author.id++;
@Property({ unique: true })
email: string;
@Property({ name: 'current_age' })
age: number;
@Property()
foo: boolean = false;
@Property({ nullable: true })
bar?: number = 123;
@OneToMany(() => Book, b => b.author)
books = new Collection<Book>(this);
constructor(email: string, age: number) {
this.email = email;
this.age = age;
}
@BeforeUpsert()
beforeUpsert() {
Author.hooks.push('beforeUpsert');
}
@AfterUpsert()
afterUpsert() {
Author.hooks.push('afterUpsert');
}
}
@Entity()
export class Book {
static id = 1;
@PrimaryKey({ name: '_id' })
id: number = Book.id++;
@ManyToOne(() => Author, { ref: true })
author: Ref<Author>;
@Property()
name: string;
constructor(name: string, author: Author) {
this.name = name;
this.author = ref(author);
}
}
@Entity()
@Unique({ properties: ['author', 'name'] })
export class FooBar {
static id = 1;
@PrimaryKey({ name: '_id' })
id: number = FooBar.id++;
@ManyToOne(() => Author, { ref: true })
author: Ref<Author>;
@Property()
name: string;
@Property({ nullable: true })
propName?: string;
constructor(name: string, author: Author | Ref<Author>) {
this.name = name;
this.author = ref(author);
}
}
@Entity()
export class FooBarWithEmbeddable {
static id = 1;
@PrimaryKey({ name: '_id' })
id: number = FooBar.id++;
@Embedded(() => FooBarEmbeddable)
fooBarEmbeddable = new FooBarEmbeddable();
}
@Embeddable()
export class FooBarEmbeddable {
@Property({ nullable: true })
name?: string;
}
class Subscriber implements EventSubscriber {
static log: any[] = [];
beforeUpsert(args: EventArgs<any>): void | Promise<void> {
Subscriber.log.push(['beforeUpsert', args]);
}
afterUpsert(args: EventArgs<any>): void | Promise<void> {
Subscriber.log.push(['afterUpsert', args]);
}
onInit(args: EventArgs<any>) {
Subscriber.log.push(['onInit', args]);
}
}
const options = {
'sqlite': { dbName: ':memory:' },
'better-sqlite': { dbName: ':memory:' },
'mysql': { dbName: 'mikro_orm_upsert', port: 3308 },
'mariadb': { dbName: 'mikro_orm_upsert', port: 3309 },
'postgresql': { dbName: 'mikro_orm_upsert' },
'mongo': { dbName: 'mikro_orm_upsert' },
};
describe.each(Utils.keys(options))('em.upsert [%s]', type => {
let orm: MikroORM;
beforeAll(async () => {
orm = await MikroORM.init<IDatabaseDriver>({
entities: [Author, Book, FooBar, FooBarWithEmbeddable],
driver: PLATFORMS[type],
loggerFactory: options => new SimpleLogger(options),
subscribers: [new Subscriber()],
...options[type],
});
await orm.schema.refreshDatabase();
});
beforeEach(async () => {
await orm.schema.clearDatabase();
Author.id = Book.id = FooBar.id = 1;
Subscriber.log.length = 0;
Author.hooks.length = 0;
});
afterAll(() => orm.close());
async function createEntities() {
const books = [
new Book('b1', new Author('a1', 31)),
new Book('b2', new Author('a2', 32)),
new Book('b3', new Author('a3', 33)),
];
const fooBars = [
new FooBar('fb1', books[0].author),
new FooBar('fb2', books[1].author),
new FooBar('fb3', books[2].author),
];
await orm.em.persist(books).persist(fooBars).flush();
expect(books.map(b => b.id)).toEqual([1, 2, 3]);
expect(books.map(b => b.author.id)).toEqual([1, 2, 3]);
expect(fooBars.map(fb => fb.id)).toEqual([1, 2, 3]);
expect(fooBars.map(fb => fb.author.id)).toEqual([1, 2, 3]);
orm.em.clear();
return books;
}
async function assert(author: Author, mock: jest.Mock) {
expect(mock.mock.calls).toMatchSnapshot();
mock.mockReset();
await orm.em.flush();
expect(mock).not.toHaveBeenCalled();
author.age = 123;
await orm.em.flush();
expect(mock).toHaveBeenCalled();
orm.em.clear();
const authors = await orm.em.find(Author, {}, { orderBy: { email: 'asc' } });
expect(authors).toHaveLength(3);
mock.mockReset();
authors[1].age = 321;
const author12 = await orm.em.upsert(authors[0]); // exists
const author22 = await orm.em.upsert(authors[1]); // exists
const author32 = await orm.em.upsert(authors[2]); // exists
expect(author12).toBe(authors[0]);
expect(author22).toBe(authors[1]);
expect(author32).toBe(authors[2]);
expect(author22.age).toBe(321);
expect(mock).not.toHaveBeenCalled();
await orm.em.flush();
await orm.em.refresh(author22);
expect(author22.age).toBe(321);
}
async function assertFooBars(fooBars: FooBar[], mock: jest.Mock) {
expect(mock.mock.calls).toMatchSnapshot();
mock.mockReset();
await orm.em.flush();
expect(mock).not.toHaveBeenCalled();
fooBars[0].propName = '12345';
await orm.em.flush();
expect(mock).toHaveBeenCalled();
orm.em.clear();
const fooBarsReloaded = await orm.em.find(FooBar, {}, { orderBy: { name: 'asc' } });
expect(fooBarsReloaded).toHaveLength(3);
mock.mockReset();
fooBarsReloaded[1].propName = '12345';
const fooBar12 = await orm.em.upsert(fooBarsReloaded[0]); // exists
const fooBar22 = await orm.em.upsert(fooBarsReloaded[1]); // exists
const fooBar32 = await orm.em.upsert(fooBarsReloaded[2]); // exists
expect(fooBar12).toBe(fooBarsReloaded[0]);
expect(fooBar22).toBe(fooBarsReloaded[1]);
expect(fooBar32).toBe(fooBarsReloaded[2]);
expect(fooBar22.propName).toBe('12345');
expect(mock).not.toHaveBeenCalled();
await orm.em.flush();
await orm.em.refresh(fooBar22);
expect(fooBar22.propName).toBe('12345');
}
test('em.upsert(Type, data) with PK', async () => {
await createEntities();
await orm.em.nativeDelete(Book, [2, 3]);
const mock = mockLogger(orm);
const author1 = await orm.em.upsert(Author, { id: 1, email: 'a1', age: 41 }); // exists
const author2 = await orm.em.upsert(Author, { id: 2, email: 'a2', age: 42 }); // inserts
const author3 = await orm.em.upsert(Author, { id: 3, email: 'a3', age: 43 }); // inserts
expect(Author.hooks).toEqual([
'beforeUpsert',
'afterUpsert',
'beforeUpsert',
'afterUpsert',
'beforeUpsert',
'afterUpsert',
]);
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsert(Type, data) with unique property', async () => {
await createEntities();
await orm.em.nativeDelete(Book, [2, 3]);
const mock = mockLogger(orm);
const author1 = await orm.em.upsert(Author, { email: 'a1', age: 41 }); // exists
const author2 = await orm.em.upsert(Author, { email: 'a2', age: 42 }); // inserts
const author3 = await orm.em.upsert(Author, { email: 'a3', age: 43 }); // inserts
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsertMany(Type, [data1, data2, data3]) with PK', async () => {
await createEntities();
await orm.em.nativeDelete(Book, [2, 3]);
const mock = mockLogger(orm);
const [author1, author2, author3] = await orm.em.upsertMany(Author, [
{ id: 1, email: 'a1', age: 41 }, // exists
{ id: 2, email: 'a2', age: 42 }, // inserts
{ id: 3, email: 'a3', age: 43 }, // inserts
]);
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['beforeUpsert', 'Object'],
['beforeUpsert', 'Object'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['onInit', 'Author'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsertMany(Type, [data1, data2, data3]) with batching', async () => {
await createEntities();
await orm.em.nativeDelete(Book, [2, 3]);
const mock = mockLogger(orm);
const data = [];
for (let i = 0; i < 1000; i++) {
data.push({ id: i + 1, email: 'a' + i, age: Math.floor(41 * Math.random()) });
}
const entities = await orm.em.upsertMany(Author, data, { batchSize: 100 });
expect(mock).toHaveBeenCalledTimes(orm.em.getPlatform().usesReturningStatement() ? 10 : 20);
expect(entities).toHaveLength(1000);
});
test('em.upsertMany(Type, [data1, data2, data3]) with unique property', async () => {
await createEntities();
await orm.em.nativeDelete(Book, [2, 3]);
const mock = mockLogger(orm);
const [author1, author2, author3] = await orm.em.upsertMany(Author, [
{ email: 'a1', age: 41 }, // exists
{ email: 'a2', age: 42 }, // inserts
{ email: 'a3', age: 43 }, // inserts
]);
expect(author1.id).toBeDefined();
expect(author2.id).toBeDefined();
expect(author3.id).toBeDefined();
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['beforeUpsert', 'Object'],
['beforeUpsert', 'Object'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['onInit', 'Author'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsert(Type, data) with unique composite property (no additional props)', async () => {
await createEntities();
await orm.em.nativeDelete(FooBar, [2, 3]);
const mock = mockLogger(orm);
const fooBar1 = await orm.em.upsert(FooBar, { name: 'fb1', author: 1 }); // exists
const fooBar2 = await orm.em.upsert(FooBar, { name: 'fb2', author: 2 }); // inserts
const fooBar3 = await orm.em.upsert(FooBar, { name: 'fb3', author: 3 }); // inserts
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['onInit', 'FooBar'],
['afterUpsert', 'FooBar'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['onInit', 'FooBar'],
['afterUpsert', 'FooBar'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['onInit', 'FooBar'],
['afterUpsert', 'FooBar'],
]);
await assertFooBars([fooBar1, fooBar2, fooBar3], mock);
});
test('em.upsert(Type, data) with unique composite property (update additional prop)', async () => {
await createEntities();
await orm.em.nativeDelete(FooBar, [2, 3]);
const mock = mockLogger(orm);
const fooBar1 = await orm.em.upsert(FooBar, { name: 'fb1', author: 1, propName: 'val 1' }); // exists
const fooBar2 = await orm.em.upsert(FooBar, { name: 'fb2', author: 2, propName: 'val 2' }); // inserts
const fooBar3 = await orm.em.upsert(FooBar, { name: 'fb3', author: 3, propName: 'val 3' }); // inserts
await assertFooBars([fooBar1, fooBar2, fooBar3], mock);
});
test('em.upsert(entity)', async () => {
await createEntities();
await orm.em.nativeDelete(Book, [2, 3]);
const mock = mockLogger(orm);
const a1 = orm.em.create(Author, { id: 1, email: 'a1', age: 41 });
const a2 = orm.em.create(Author, { id: 2, email: 'a2', age: 42 });
const a3 = orm.em.create(Author, { id: 3, email: 'a3', age: 43 });
const author1 = await orm.em.upsert(a1); // exists
const author2 = await orm.em.upsert(a2); // inserts
const author3 = await orm.em.upsert(a3); // inserts
expect(a1).toBe(author1);
expect(a2).toBe(author2);
expect(a3).toBe(author3);
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['onInit', 'Author'],
['onInit', 'Author'],
['onInit', 'Author'],
['beforeUpsert', 'Object'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsertMany([entity1, entity2, entity3]) with PK', async () => {
await createEntities();
await orm.em.nativeDelete(Book, [2, 3]);
const mock = mockLogger(orm);
const a1 = orm.em.create(Author, { id: 1, email: 'a1', age: 41 }); // exists
const a2 = orm.em.create(Author, { id: 2, email: 'a2', age: 42 }); // inserts
const a3 = orm.em.create(Author, { id: 3, email: 'a3', age: 43 }); // inserts
const [author1, author2, author3] = await orm.em.upsertMany([a1, a2, a3]);
expect(a1).toBe(author1);
expect(a2).toBe(author2);
expect(a3).toBe(author3);
await assert(author2, mock);
});
test('em.upsertMany([entity1, entity2, entity3]) with unique property', async () => {
await createEntities();
await orm.em.nativeDelete(Book, [2, 3]);
const mock = mockLogger(orm);
const a1 = orm.em.create(Author, { email: 'a1', age: 41 }); // exists
delete (a1 as any).id; // simulate unknown pk
const a2 = orm.em.create(Author, { email: 'a2', age: 42 }); // inserts
delete (a2 as any).id; // simulate unknown pk
const a3 = orm.em.create(Author, { email: 'a3', age: 43 }); // inserts
delete (a3 as any).id; // simulate unknown pk
const [author1, author2, author3] = await orm.em.upsertMany([a1, a2, a3]);
expect(a1).toBe(author1);
expect(a1.id).toBeDefined();
expect(a2).toBe(author2);
expect(a2.id).toBeDefined();
expect(a3).toBe(author3);
expect(a3.id).toBeDefined();
await assert(author2, mock);
});
test('em.upsert(entity) with unique composite property', async () => {
await createEntities();
await orm.em.nativeDelete(FooBar, [2, 3]);
const mock = mockLogger(orm);
const fb1 = orm.em.create(FooBar, { id: 1, name: 'fb1', author: 1, propName: 'val 1' });
const fb2 = orm.em.create(FooBar, { id: 2, name: 'fb2', author: 2, propName: 'val 2' });
const fb3 = orm.em.create(FooBar, { id: 3, name: 'fb3', author: 3, propName: 'val 3' });
const fooBar1 = await orm.em.upsert(FooBar, fb1); // exists
const fooBar2 = await orm.em.upsert(FooBar, fb2); // inserts
const fooBar3 = await orm.em.upsert(FooBar, fb3); // inserts
expect(fb1).toBe(fooBar1);
expect(fb2).toBe(fooBar2);
expect(fb3).toBe(fooBar3);
await assertFooBars([fooBar1, fooBar2, fooBar3], mock);
});
test('em.upsert(entity) with embeddable', async () => {
const testEntity = orm.em.create(FooBarWithEmbeddable, { fooBarEmbeddable: {} });
await orm.em.upsert(testEntity);
expect(testEntity.id).toBeDefined();
const [insertedEntity2] = await orm.em.upsertMany(FooBarWithEmbeddable, [{ id: testEntity.id, fooBarEmbeddable: {} }]);
expect(insertedEntity2).toBe(testEntity);
});
test('em.upsert(Type, entity, options) with advanced options', async () => {
await createEntities();
const mock = mockLogger(orm);
const a1 = orm.em.create(Author, { id: 1, email: 'a1', age: 41, foo: true });
const a2 = orm.em.create(Author, { id: 2, email: 'a2', age: 42, foo: true });
const a3 = orm.em.create(Author, { id: 5, email: 'a3', age: 43, foo: true }); // different PK
const author1 = await orm.em.upsert(Author, a1, {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'ignore',
}); // exists, dont update anything
const author2 = await orm.em.upsert(Author, a2, {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'merge',
onConflictMergeFields: ['age'],
}); // exists, update age only
// match by email, PK differs and is omitted from the merge fields
const author3 = await orm.em.upsert(Author, a3, {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'merge',
onConflictExcludeFields: ['id'],
}); // exists, update age only, override the value from the entity
expect(a1).toBe(author1);
expect(a2).toBe(author2);
expect(a3).toBe(author3);
expect(author1).toMatchObject({ id: 1, age: 31, foo: false }); // ignore
expect(author2).toMatchObject({ id: 2, age: 42, foo: false }); // merge only `age`
expect(author3).toMatchObject({ id: 3, age: 43, foo: true }); // merge without `id`, different PK should be ignored
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['onInit', 'Author'],
['onInit', 'Author'],
['onInit', 'Author'],
['beforeUpsert', 'Object'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsert(Type, data, options) with advanced options', async () => {
await createEntities();
const mock = mockLogger(orm);
const a1 = { id: 1, email: 'a1', age: 41, foo: true };
const a2 = { id: 2, email: 'a2', age: 42, foo: true };
const a3 = { id: 5, email: 'a3', age: 43, foo: true }; // different PK
const author1 = await orm.em.upsert(Author, a1, {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'ignore',
}); // exists, dont update anything
const author2 = await orm.em.upsert<Author>(Author, a2, {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'merge',
onConflictMergeFields: ['age'],
}); // exists, update age only
// match by email, PK differs and is omitted from the merge fields
const author3 = await orm.em.upsert(Author, a3, {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'merge',
onConflictExcludeFields: ['id'],
}); // exists, update age only, override the value from the entity
expect(author1).toMatchObject({ id: 1, age: 31, foo: false }); // ignore
expect(author2).toMatchObject({ id: 2, age: 42, foo: false }); // merge only `age`
expect(author3).toMatchObject({ id: 3, age: 43, foo: true }); // merge without `id`, different PK should be ignored
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
['beforeUpsert', 'Object'],
['onInit', 'Author'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsertMany(Type, [entity], options) with advanced options (ignore)', async () => {
await createEntities();
const mock = mockLogger(orm);
const a1 = orm.em.create(Author, { id: 1, email: 'a1', age: 41, foo: true });
const a2 = orm.em.create(Author, { id: 2, email: 'a2', age: 42, foo: true });
const a3 = orm.em.create(Author, { id: 5, email: 'a3', age: 43, foo: true }); // different PK
const [author1, author2, author3] = await orm.em.upsertMany(Author, [a1, a2, a3], {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'ignore',
}); // exists, dont update anything
expect(a1).toBe(author1);
expect(a2).toBe(author2);
expect(a3).toBe(author3);
expect(author1).toMatchObject({ id: 1, age: 31, foo: false });
expect(author2).toMatchObject({ id: 2, age: 32, foo: false });
expect(author3).toMatchObject({ id: 3, age: 33, foo: false });
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['onInit', 'Author'],
['onInit', 'Author'],
['onInit', 'Author'],
['beforeUpsert', 'Author'],
['beforeUpsert', 'Author'],
['beforeUpsert', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsertMany(Type, [entity], options) with advanced options (onConflictMergeFields)', async () => {
await createEntities();
const mock = mockLogger(orm);
const a1 = orm.em.create(Author, { id: 1, email: 'a1', age: 41, foo: true });
const a2 = orm.em.create(Author, { id: 2, email: 'a2', age: 42, foo: true });
const a3 = orm.em.create(Author, { id: 5, email: 'a3', age: 43, foo: true }); // different PK
const [author1, author2, author3] = await orm.em.upsertMany(Author, [a1, a2, a3], {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'merge',
onConflictMergeFields: ['age'],
}); // exists, update age only
expect(a1).toBe(author1);
expect(a2).toBe(author2);
expect(a3).toBe(author3);
expect(author1).toMatchObject({ id: 1, age: 41, foo: false });
expect(author2).toMatchObject({ id: 2, age: 42, foo: false });
expect(author3).toMatchObject({ id: 3, age: 43, foo: false });
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['onInit', 'Author'],
['onInit', 'Author'],
['onInit', 'Author'],
['beforeUpsert', 'Author'],
['beforeUpsert', 'Author'],
['beforeUpsert', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsertMany(Type, [entity], options) with advanced options (onConflictExcludeFields)', async () => {
await createEntities();
const mock = mockLogger(orm);
const a1 = orm.em.create(Author, { id: 1, email: 'a1', age: 41, foo: true });
const a2 = orm.em.create(Author, { id: 2, email: 'a2', age: 42, foo: true });
const a3 = orm.em.create(Author, { id: 5, email: 'a3', age: 43, foo: true }); // different PK
// // match by email, PK differs and is omitted from the merge fields
const [author1, author2, author3] = await orm.em.upsertMany(Author, [a1, a2, a3], {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'merge',
onConflictExcludeFields: ['id'],
}); // exists, update age only, override the value from the entity
expect(a1).toBe(author1);
expect(a2).toBe(author2);
expect(a3).toBe(author3);
expect(author1).toMatchObject({ id: 1, age: 41, foo: true });
expect(author2).toMatchObject({ id: 2, age: 42, foo: true });
expect(author3).toMatchObject({ id: 3, age: 43, foo: true });
expect(Subscriber.log.map(l => [l[0], l[1].entity.constructor.name])).toEqual([
['onInit', 'Author'],
['onInit', 'Author'],
['onInit', 'Author'],
['beforeUpsert', 'Author'],
['beforeUpsert', 'Author'],
['beforeUpsert', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
['afterUpsert', 'Author'],
]);
await assert(author2, mock);
});
test('em.upsertMany(Type, [data], options) with advanced options (ignore)', async () => {
await createEntities();
const mock = mockLogger(orm);
const a1 = { id: 1, email: 'a1', age: 41, foo: true };
const a2 = { id: 2, email: 'a2', age: 42, foo: true };
const a3 = { id: 5, email: 'a3', age: 43, foo: true }; // different PK
const [author1, author2, author3] = await orm.em.upsertMany(Author, [a1, a2, a3], {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'ignore',
}); // exists, dont update anything
expect(author1).toMatchObject({ id: 1, age: 31, foo: false }); // ignore
expect(author2).toMatchObject({ id: 2, age: 32, foo: false }); // merge only `age`
expect(author3).toMatchObject({ id: 3, age: 33, foo: false }); // merge without `id`, different PK should be ignored
await assert(author2 as Author, mock);
});
test('em.upsertMany(Type, [data], options) with advanced options (onConflictMergeFields)', async () => {
await createEntities();
const mock = mockLogger(orm);
const a1 = { id: 1, email: 'a1', age: 41, foo: true };
const a2 = { id: 2, email: 'a2', age: 42, foo: true };
const a3 = { id: 5, email: 'a3', age: 43, foo: true }; // different PK
const [author1, author2, author3] = await orm.em.upsertMany(Author, [a1, a2, a3], {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'merge',
onConflictMergeFields: ['age'],
}); // exists, update age only
expect(author1).toMatchObject({ id: 1, age: 41, foo: false });
expect(author2).toMatchObject({ id: 2, age: 42, foo: false });
expect(author3).toMatchObject({ id: 3, age: 43, foo: false });
await assert(author2 as Author, mock);
});
test('em.upsertMany(Type, [data], options) with advanced options (onConflictExcludeFields)', async () => {
await createEntities();
const mock = mockLogger(orm);
const a1 = { id: 1, email: 'a1', age: 41, foo: true };
const a2 = { id: 2, email: 'a2', age: 42, foo: true };
const a3 = { id: 5, email: 'a3', age: 43, foo: true }; // different PK
// match by email, PK differs and is omitted from the merge fields
const [author1, author2, author3] = await orm.em.upsertMany(Author, [a1, a2, a3], {
onConflictFields: ['email'], // specify a manual set of fields pass to the on conflict clause
onConflictAction: 'merge',
onConflictExcludeFields: ['id'],
}); // exists, update age only, override the value from the entity
expect(author1).toMatchObject({ id: 1, age: 41, foo: true });
expect(author2).toMatchObject({ id: 2, age: 42, foo: true });
expect(author3).toMatchObject({ id: 3, age: 43, foo: true });
await assert(author2 as Author, mock);
});
});