Skip to content

Commit

Permalink
fix(mapping): support mixed M:N with composite PK on one side only
Browse files Browse the repository at this point in the history
When populating m:n relation in some cases was error, where wrong metadata used to check is there composite PK involved in query, which caused some cryptic errors.
  • Loading branch information
whocaresk authored and B4nan committed Aug 9, 2020
1 parent cd8c683 commit a951918
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 6 deletions.
4 changes: 2 additions & 2 deletions tests/MikroORM.test.ts
Expand Up @@ -5,7 +5,7 @@ import { TsMorphMetadataProvider } from '@mikro-orm/reflection';
import { MikroORM, EntityManager, Configuration } from '@mikro-orm/core';
import { Author, Test } from './entities';
import { BASE_DIR } from './bootstrap';
import { Author2, Car2, CarOwner2, FooBaz2, User2 } from './entities-sql';
import { Author2, Car2, CarOwner2, FooBaz2, Sandwich, User2 } from './entities-sql';
import { BaseEntity2 } from './entities-sql/BaseEntity2';

describe('MikroORM', () => {
Expand Down Expand Up @@ -47,7 +47,7 @@ describe('MikroORM', () => {
dbName: 'not-found',
baseDir: BASE_DIR,
type: 'mysql',
entities: [Car2, CarOwner2, User2],
entities: [Car2, CarOwner2, User2, Sandwich],
debug: ['info'],
logger,
});
Expand Down
31 changes: 31 additions & 0 deletions tests/__snapshots__/EntityGenerator.test.ts.snap
Expand Up @@ -385,6 +385,22 @@ export class Publisher2Tests {
@ManyToOne({ entity: () => Test2, cascade: [Cascade.ALL], index: 'publisher2_tests_test2_id_index' })
test2!: Test2;
}
",
"import { Entity, PrimaryKey, Property } from 'mikro-orm';
@Entity()
export class Sandwich {
@PrimaryKey()
id!: number;
@Property({ length: 255 })
name!: string;
@Property()
price!: number;
}
",
"import { Cascade, Entity, OneToOne, PrimaryKey, Property } from 'mikro-orm';
Expand Down Expand Up @@ -445,6 +461,21 @@ export class User2Cars {
@ManyToOne({ entity: () => Car2, cascade: [Cascade.ALL], primary: true, index: 'user2_cars_car2_name_car2_year_index' })
car2!: Car2;
}
",
"import { Cascade, Entity, ManyToOne } from 'mikro-orm';
import { Sandwich } from './Sandwich';
import { User2 } from './User2';
@Entity()
export class User2Sandwiches {
@ManyToOne({ entity: () => User2, cascade: [Cascade.ALL], primary: true, index: 'user2_sandwiches_user2_first_name_user2_last_name_index' })
user2!: User2;
@ManyToOne({ entity: () => Sandwich, cascade: [Cascade.ALL], primary: true, index: 'user2_sandwiches_sandwich_id_index' })
sandwich!: Sandwich;
}
",
]
Expand Down
37 changes: 37 additions & 0 deletions tests/__snapshots__/SchemaGenerator.test.ts.snap
Expand Up @@ -71,6 +71,8 @@ alter table \`base_user2\` add index \`base_user2_favourite_employee_id_index\`(
alter table \`base_user2\` add index \`base_user2_favourite_manager_id_index\`(\`favourite_manager_id\`);
alter table \`base_user2\` add unique \`base_user2_favourite_manager_id_unique\`(\`favourite_manager_id\`);
create table \`sandwich\` (\`id\` int unsigned not null auto_increment primary key, \`name\` varchar(255) not null, \`price\` int(11) not null) default character set utf8mb4 engine = InnoDB;
create table \`author_to_friend\` (\`author2_1_id\` int(11) unsigned not null, \`author2_2_id\` int(11) unsigned not null) default character set utf8mb4 engine = InnoDB;
alter table \`author_to_friend\` add index \`author_to_friend_author2_1_id_index\`(\`author2_1_id\`);
alter table \`author_to_friend\` add index \`author_to_friend_author2_2_id_index\`(\`author2_2_id\`);
Expand Down Expand Up @@ -99,6 +101,11 @@ alter table \`user2_cars\` add primary key \`user2_cars_pkey\`(\`user2_first_nam
alter table \`user2_cars\` add index \`user2_cars_user2_first_name_user2_last_name_index\`(\`user2_first_name\`, \`user2_last_name\`);
alter table \`user2_cars\` add index \`user2_cars_car2_name_car2_year_index\`(\`car2_name\`, \`car2_year\`);
create table \`user2_sandwiches\` (\`user2_first_name\` varchar(100) not null, \`user2_last_name\` varchar(100) not null, \`sandwich_id\` int(11) unsigned not null) default character set utf8mb4 engine = InnoDB;
alter table \`user2_sandwiches\` add index \`user2_sandwiches_sandwich_id_index\`(\`sandwich_id\`);
alter table \`user2_sandwiches\` add primary key \`user2_sandwiches_pkey\`(\`user2_first_name\`, \`user2_last_name\`, \`sandwich_id\`);
alter table \`user2_sandwiches\` add index \`user2_sandwiches_user2_first_name_user2_last_name_index\`(\`user2_first_name\`, \`user2_last_name\`);
alter table \`author2\` add constraint \`author2_favourite_book_uuid_pk_foreign\` foreign key (\`favourite_book_uuid_pk\`) references \`book2\` (\`uuid_pk\`) on update no action on delete cascade;
alter table \`author2\` add constraint \`author2_favourite_author_id_foreign\` foreign key (\`favourite_author_id\`) references \`author2\` (\`id\`) on update cascade on delete set null;
Expand Down Expand Up @@ -143,6 +150,10 @@ alter table \`user2_cars\` add constraint \`user2_cars_user2_last_name_foreign\`
alter table \`user2_cars\` add constraint \`user2_cars_car2_name_foreign\` foreign key (\`car2_name\`) references \`car2\` (\`name\`) on update cascade on delete cascade;
alter table \`user2_cars\` add constraint \`user2_cars_car2_year_foreign\` foreign key (\`car2_year\`) references \`car2\` (\`year\`) on update cascade on delete cascade;
alter table \`user2_sandwiches\` add constraint \`user2_sandwiches_user2_first_name_foreign\` foreign key (\`user2_first_name\`) references \`user2\` (\`first_name\`) on update cascade on delete cascade;
alter table \`user2_sandwiches\` add constraint \`user2_sandwiches_user2_last_name_foreign\` foreign key (\`user2_last_name\`) references \`user2\` (\`last_name\`) on update cascade on delete cascade;
alter table \`user2_sandwiches\` add constraint \`user2_sandwiches_sandwich_id_foreign\` foreign key (\`sandwich_id\`) references \`sandwich\` (\`id\`) on update cascade on delete cascade;
set foreign_key_checks = 1;
"
`;
Expand All @@ -165,12 +176,14 @@ drop table if exists \`car2\`;
drop table if exists \`car_owner2\`;
drop table if exists \`user2\`;
drop table if exists \`base_user2\`;
drop table if exists \`sandwich\`;
drop table if exists \`author_to_friend\`;
drop table if exists \`author2_following\`;
drop table if exists \`book2_tags\`;
drop table if exists \`book_to_tag_unordered\`;
drop table if exists \`publisher2_tests\`;
drop table if exists \`user2_cars\`;
drop table if exists \`user2_sandwiches\`;
set foreign_key_checks = 1;
"
Expand All @@ -194,12 +207,14 @@ drop table if exists \`car2\`;
drop table if exists \`car_owner2\`;
drop table if exists \`user2\`;
drop table if exists \`base_user2\`;
drop table if exists \`sandwich\`;
drop table if exists \`author_to_friend\`;
drop table if exists \`author2_following\`;
drop table if exists \`book2_tags\`;
drop table if exists \`book_to_tag_unordered\`;
drop table if exists \`publisher2_tests\`;
drop table if exists \`user2_cars\`;
drop table if exists \`user2_sandwiches\`;
create table \`author2\` (\`id\` int unsigned not null auto_increment primary key, \`created_at\` datetime(3) not null default current_timestamp(3), \`updated_at\` datetime(3) not null default current_timestamp(3), \`name\` varchar(255) not null, \`email\` varchar(255) not null, \`age\` int(11) null default null, \`terms_accepted\` tinyint(1) not null default false, \`optional\` tinyint(1) null, \`identities\` text null, \`born\` date null, \`born_time\` time null, \`favourite_book_uuid_pk\` varchar(36) null, \`favourite_author_id\` int(11) unsigned null) default character set utf8mb4 engine = InnoDB;
alter table \`author2\` add index \`custom_email_index_name\`(\`email\`);
Expand Down Expand Up @@ -268,6 +283,8 @@ alter table \`base_user2\` add index \`base_user2_favourite_employee_id_index\`(
alter table \`base_user2\` add index \`base_user2_favourite_manager_id_index\`(\`favourite_manager_id\`);
alter table \`base_user2\` add unique \`base_user2_favourite_manager_id_unique\`(\`favourite_manager_id\`);
create table \`sandwich\` (\`id\` int unsigned not null auto_increment primary key, \`name\` varchar(255) not null, \`price\` int(11) not null) default character set utf8mb4 engine = InnoDB;
create table \`author_to_friend\` (\`author2_1_id\` int(11) unsigned not null, \`author2_2_id\` int(11) unsigned not null) default character set utf8mb4 engine = InnoDB;
alter table \`author_to_friend\` add index \`author_to_friend_author2_1_id_index\`(\`author2_1_id\`);
alter table \`author_to_friend\` add index \`author_to_friend_author2_2_id_index\`(\`author2_2_id\`);
Expand Down Expand Up @@ -296,6 +313,11 @@ alter table \`user2_cars\` add primary key \`user2_cars_pkey\`(\`user2_first_nam
alter table \`user2_cars\` add index \`user2_cars_user2_first_name_user2_last_name_index\`(\`user2_first_name\`, \`user2_last_name\`);
alter table \`user2_cars\` add index \`user2_cars_car2_name_car2_year_index\`(\`car2_name\`, \`car2_year\`);
create table \`user2_sandwiches\` (\`user2_first_name\` varchar(100) not null, \`user2_last_name\` varchar(100) not null, \`sandwich_id\` int(11) unsigned not null) default character set utf8mb4 engine = InnoDB;
alter table \`user2_sandwiches\` add index \`user2_sandwiches_sandwich_id_index\`(\`sandwich_id\`);
alter table \`user2_sandwiches\` add primary key \`user2_sandwiches_pkey\`(\`user2_first_name\`, \`user2_last_name\`, \`sandwich_id\`);
alter table \`user2_sandwiches\` add index \`user2_sandwiches_user2_first_name_user2_last_name_index\`(\`user2_first_name\`, \`user2_last_name\`);
alter table \`author2\` add constraint \`author2_favourite_book_uuid_pk_foreign\` foreign key (\`favourite_book_uuid_pk\`) references \`book2\` (\`uuid_pk\`) on update no action on delete cascade;
alter table \`author2\` add constraint \`author2_favourite_author_id_foreign\` foreign key (\`favourite_author_id\`) references \`author2\` (\`id\`) on update cascade on delete set null;
Expand Down Expand Up @@ -340,6 +362,10 @@ alter table \`user2_cars\` add constraint \`user2_cars_user2_last_name_foreign\`
alter table \`user2_cars\` add constraint \`user2_cars_car2_name_foreign\` foreign key (\`car2_name\`) references \`car2\` (\`name\`) on update cascade on delete cascade;
alter table \`user2_cars\` add constraint \`user2_cars_car2_year_foreign\` foreign key (\`car2_year\`) references \`car2\` (\`year\`) on update cascade on delete cascade;
alter table \`user2_sandwiches\` add constraint \`user2_sandwiches_user2_first_name_foreign\` foreign key (\`user2_first_name\`) references \`user2\` (\`first_name\`) on update cascade on delete cascade;
alter table \`user2_sandwiches\` add constraint \`user2_sandwiches_user2_last_name_foreign\` foreign key (\`user2_last_name\`) references \`user2\` (\`last_name\`) on update cascade on delete cascade;
alter table \`user2_sandwiches\` add constraint \`user2_sandwiches_sandwich_id_foreign\` foreign key (\`sandwich_id\`) references \`sandwich\` (\`id\`) on update cascade on delete cascade;
set foreign_key_checks = 1;
"
`;
Expand Down Expand Up @@ -940,6 +966,8 @@ alter table \`base_user2\` add index \`base_user2_favourite_employee_id_index\`(
alter table \`base_user2\` add index \`base_user2_favourite_manager_id_index\`(\`favourite_manager_id\`);
alter table \`base_user2\` add unique \`base_user2_favourite_manager_id_unique\`(\`favourite_manager_id\`);
create table \`sandwich\` (\`id\` int unsigned not null auto_increment primary key, \`name\` varchar(255) not null, \`price\` int(11) not null) default character set utf8mb4 engine = InnoDB;
create table \`author_to_friend\` (\`author2_1_id\` int(11) unsigned not null, \`author2_2_id\` int(11) unsigned not null) default character set utf8mb4 engine = InnoDB;
alter table \`author_to_friend\` add index \`author_to_friend_author2_1_id_index\`(\`author2_1_id\`);
alter table \`author_to_friend\` add index \`author_to_friend_author2_2_id_index\`(\`author2_2_id\`);
Expand Down Expand Up @@ -968,6 +996,11 @@ alter table \`user2_cars\` add primary key \`user2_cars_pkey\`(\`user2_first_nam
alter table \`user2_cars\` add index \`user2_cars_user2_first_name_user2_last_name_index\`(\`user2_first_name\`, \`user2_last_name\`);
alter table \`user2_cars\` add index \`user2_cars_car2_name_car2_year_index\`(\`car2_name\`, \`car2_year\`);
create table \`user2_sandwiches\` (\`user2_first_name\` varchar(100) not null, \`user2_last_name\` varchar(100) not null, \`sandwich_id\` int(11) unsigned not null) default character set utf8mb4 engine = InnoDB;
alter table \`user2_sandwiches\` add index \`user2_sandwiches_sandwich_id_index\`(\`sandwich_id\`);
alter table \`user2_sandwiches\` add primary key \`user2_sandwiches_pkey\`(\`user2_first_name\`, \`user2_last_name\`, \`sandwich_id\`);
alter table \`user2_sandwiches\` add index \`user2_sandwiches_user2_first_name_user2_last_name_index\`(\`user2_first_name\`, \`user2_last_name\`);
alter table \`author2\` add constraint \`author2_favourite_book_uuid_pk_foreign\` foreign key (\`favourite_book_uuid_pk\`) references \`book2\` (\`uuid_pk\`) on update no action on delete cascade;
alter table \`author2\` add constraint \`author2_favourite_author_id_foreign\` foreign key (\`favourite_author_id\`) references \`author2\` (\`id\`) on update cascade on delete set null;
Expand Down Expand Up @@ -1012,6 +1045,10 @@ alter table \`user2_cars\` add constraint \`user2_cars_user2_last_name_foreign\`
alter table \`user2_cars\` add constraint \`user2_cars_car2_name_foreign\` foreign key (\`car2_name\`) references \`car2\` (\`name\`) on update cascade on delete cascade;
alter table \`user2_cars\` add constraint \`user2_cars_car2_year_foreign\` foreign key (\`car2_year\`) references \`car2\` (\`year\`) on update cascade on delete cascade;
alter table \`user2_sandwiches\` add constraint \`user2_sandwiches_user2_first_name_foreign\` foreign key (\`user2_first_name\`) references \`user2\` (\`first_name\`) on update cascade on delete cascade;
alter table \`user2_sandwiches\` add constraint \`user2_sandwiches_user2_last_name_foreign\` foreign key (\`user2_last_name\`) references \`user2\` (\`last_name\`) on update cascade on delete cascade;
alter table \`user2_sandwiches\` add constraint \`user2_sandwiches_sandwich_id_foreign\` foreign key (\`sandwich_id\`) references \`sandwich\` (\`id\`) on update cascade on delete cascade;
set foreign_key_checks = 1;
"
`;
Expand Down
4 changes: 2 additions & 2 deletions tests/bootstrap.ts
Expand Up @@ -10,7 +10,7 @@ import { PostgreSqlDriver } from '@mikro-orm/postgresql';
import { Author, Book, BookTag, Publisher, Test } from './entities';
import {
Author2, Book2, BookTag2, FooBar2, FooBaz2, Publisher2, Test2, Label2, Configuration2, Address2, FooParam2,
Car2, CarOwner2, User2, BaseUser2, Employee2, Manager2, CompanyOwner2,
Car2, CarOwner2, User2, BaseUser2, Employee2, Manager2, CompanyOwner2, Sandwich,
} from './entities-sql';
import { BaseEntity2 } from './entities-sql/BaseEntity2';
import { BaseEntity22 } from './entities-sql/BaseEntity22';
Expand Down Expand Up @@ -55,7 +55,7 @@ export async function initORMMySql<D extends MySqlDriver | MariaDbDriver = MySql
let orm = await MikroORM.init<AbstractSqlDriver>({
entities: [
Author2, Address2, Book2, BookTag2, Publisher2, Test2, FooBar2, FooBaz2, FooParam2, Configuration2, BaseEntity2, BaseEntity22,
Car2, CarOwner2, User2, BaseUser2, Employee2, Manager2, CompanyOwner2,
Car2, CarOwner2, User2, BaseUser2, Employee2, Manager2, CompanyOwner2, Sandwich,
],
discovery: { tsConfigPath: BASE_DIR + '/tsconfig.test.json' },
clientUrl: `mysql://root@127.0.0.1:3306/mikro_orm_test`,
Expand Down
65 changes: 63 additions & 2 deletions tests/composite-keys.mysql.test.ts
@@ -1,6 +1,18 @@
import { MikroORM, wrap } from '@mikro-orm/core';
import { MySqlDriver } from '@mikro-orm/mysql';
import { Author2, Configuration2, FooBar2, FooBaz2, FooParam2, Test2, Address2, Car2, CarOwner2, User2 } from './entities-sql';
import {
Author2,
Configuration2,
FooBar2,
FooBaz2,
FooParam2,
Test2,
Address2,
Car2,
CarOwner2,
User2,
Sandwich,
} from './entities-sql';
import { initORMMySql, wipeDatabaseMySql } from './bootstrap';

describe('composite keys in mysql', () => {
Expand Down Expand Up @@ -135,7 +147,7 @@ describe('composite keys in mysql', () => {
expect(c3).toBeNull();
});

test('composite entity in m:n relationship', async () => {
test('composite entity in m:n relationship, both entities are composite', async () => {
const car1 = new Car2('Audi A8', 2011, 100_000);
const car2 = new Car2('Audi A8', 2012, 150_000);
const car3 = new Car2('Audi A8', 2013, 200_000);
Expand Down Expand Up @@ -184,6 +196,55 @@ describe('composite keys in mysql', () => {
expect(c3).toBeNull();
});

test('composite entity in m:n relationship, one of entity is composite', async () => {
const sandwich1 = new Sandwich('Fish Sandwich', 100);
const sandwich2 = new Sandwich('Fried Egg Sandwich', 200);
const sandwich3 = new Sandwich('Grilled Cheese Sandwich', 300);
const user1 = new User2('Henry', 'Doe 1');
const user2 = new User2('Henry', 'Doe 2');
const user3 = new User2('Henry', 'Doe 3');
user1.sandwiches.add(sandwich1, sandwich3);
user2.sandwiches.add(sandwich3);
user2.sandwiches.add(sandwich2, sandwich3);
await orm.em.persistAndFlush([user1, user2, user3]);
orm.em.clear();

const u1 = await orm.em.findOneOrFail(User2, user1, ['sandwiches']);
expect(u1.sandwiches.getItems()).toMatchObject([
{ name: 'Fish Sandwich', price: 100 },
{ name: 'Grilled Cheese Sandwich', price: 300 },
]);
expect(wrap(u1).toJSON()).toMatchObject({
firstName: 'Henry',
lastName: 'Doe 1',
foo: null,
sandwiches: [
{ name: 'Fish Sandwich', price: 100 },
{ name: 'Grilled Cheese Sandwich', price: 300 },
],
});

u1.foo = 321;
u1.sandwiches[0].price = 200;
await orm.em.flush();
orm.em.clear();

const u2 = await orm.em.findOneOrFail(User2, u1, ['sandwiches']);
expect(u2.sandwiches[0].price).toBe(200);

const c1 = await orm.em.findOneOrFail(Sandwich, { id: sandwich1.id });
expect(c1).toBe(u2.sandwiches[0]);

await orm.em.removeEntity(u2, true);
const o3 = await orm.em.findOne(User2, u1);
expect(o3).toBeNull();
const c2 = await orm.em.findOneOrFail(Sandwich, sandwich1,['users']);
expect(c2).toBe(u2.sandwiches[0]);
await orm.em.removeEntity(c2, true);
const c3 = await orm.em.findOne(Sandwich, sandwich1);
expect(c3).toBeNull();
});

test('composite key references', async () => {
const ref = orm.em.getReference(Car2, ['n', 1], true);
expect(ref.unwrap()).toBeInstanceOf(Car2);
Expand Down
4 changes: 4 additions & 0 deletions tests/entities-sql/User2.ts
@@ -1,5 +1,6 @@
import { Collection, Entity, ManyToMany, PrimaryKey, Property } from '@mikro-orm/core';
import { Car2 } from './Car2';
import { Sandwich } from './sandwich';

@Entity()
export class User2 {
Expand All @@ -16,6 +17,9 @@ export class User2 {
@ManyToMany(() => Car2)
cars = new Collection<Car2>(this);

@ManyToMany(() => Sandwich)
sandwiches = new Collection<Sandwich>(this);

constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
Expand Down
1 change: 1 addition & 0 deletions tests/entities-sql/index.ts
Expand Up @@ -16,3 +16,4 @@ export * from './BaseUser2';
export * from './Employee2';
export * from './Manager2';
export * from './CompanyOwner2';
export * from './sandwich';
24 changes: 24 additions & 0 deletions tests/entities-sql/sandwich.ts
@@ -0,0 +1,24 @@
import { Collection, Entity, ManyToMany, PrimaryKey, Property } from '@mikro-orm/core';
import { User2 } from './User2';

@Entity()
export class Sandwich {

@PrimaryKey()
id!: number;

@Property({ length: 255 })
name: string;

@Property()
price: number;

@ManyToMany(() => User2, u => u.sandwiches)
users: Collection<User2> = new Collection<User2>(this);

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

}

0 comments on commit a951918

Please sign in to comment.