Skip to content

Commit

Permalink
fix(core): support pivot entities with autoincrement PK
Browse files Browse the repository at this point in the history
Closes #4988
  • Loading branch information
B4nan committed Dec 21, 2023
1 parent b60e4ee commit e250634
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 6 deletions.
24 changes: 18 additions & 6 deletions packages/core/src/metadata/MetadataDiscovery.ts
Expand Up @@ -394,7 +394,9 @@ export class MetadataDiscovery {
const meta2 = this.metadata.get(prop.type);
Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
const pivotMeta = this.metadata.find(prop.pivotEntity);
const pks = Object.values(pivotMeta?.properties ?? {}).filter(p => p.primary);
const props = Object.values(pivotMeta?.properties ?? {});
const pks = props.filter(p => p.primary);
const fks = props.filter(p => p.reference === ReferenceType.MANY_TO_ONE);

if (pivotMeta) {
pivotMeta.pivotTable = true;
Expand All @@ -406,11 +408,11 @@ export class MetadataDiscovery {
}
}

if (pivotMeta && pks.length === 2) {
if (pivotMeta && (pks.length === 2 || fks.length >= 2)) {
const owner = prop.mappedBy ? meta2.properties[prop.mappedBy] : prop;
const [first, second] = this.ensureCorrectFKOrderInPivotEntity(pivotMeta, owner);
prop.joinColumns = first.fieldNames;
prop.inverseJoinColumns = second.fieldNames;
prop.joinColumns = first!.fieldNames;
prop.inverseJoinColumns = second!.fieldNames;
}

if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
Expand Down Expand Up @@ -541,8 +543,18 @@ export class MetadataDiscovery {
});
}

private ensureCorrectFKOrderInPivotEntity(meta: EntityMetadata, owner: EntityProperty): [EntityProperty, EntityProperty] {
let [first, second] = Object.values(meta.properties).filter(p => p.primary);
private ensureCorrectFKOrderInPivotEntity(meta: EntityMetadata, owner: EntityProperty): [] | [EntityProperty, EntityProperty] {
const pks = Object.values(meta.properties).filter(p => p.primary);
const fks = Object.values(meta.properties).filter(p => p.reference === ReferenceType.MANY_TO_ONE);
let first, second;

if (pks.length === 2) {
[first, second] = pks;
} else if (fks.length >= 2) {
[first, second] = fks;
} else {
return [];
}

// wrong FK order, first FK needs to point to the owning side
// (note that we can detect this only if the FKs target different types)
Expand Down
130 changes: 130 additions & 0 deletions tests/issues/GH4988.test.ts
@@ -0,0 +1,130 @@
import { BigIntType, Collection, EntitySchema, Ref } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/postgresql';

class ProductEntity {

readonly id!: number;
readonly name!: string;

constructor(props: { name: string; id?: number }) {
Object.assign(this, props);
}

}

class Company {

readonly id!: number;
readonly name!: string;
readonly products = new Collection<ProductEntity>(this);

constructor(props: { name: string; id?: number }) {
Object.assign(this, props);
}

}

class CompanyProduct {

readonly id!: number;
readonly product!: Ref<ProductEntity>;
readonly company!: Ref<CompanyProduct>;
readonly createdAt!: Date;
readonly updatedAt!: Date;

}

const productSchema = new EntitySchema({
class: ProductEntity,
tableName: 'product',
properties: {
id: {
type: BigIntType,
primary: true,
},
name: {
type: String,
},
},
});

const companySchema = new EntitySchema({
class: Company,
tableName: 'company',
properties: {
id: {
type: BigIntType,
primary: true,
},
name: {
type: String,
},
products: {
reference: 'm:n',
entity: () => ProductEntity,
pivotEntity: () => CompanyProduct,
fixedOrder: true,
},
},
});

const companyProductsSchema = new EntitySchema({
class: CompanyProduct,
uniques: [
{ name: 'uniqueCompanyProduct', properties: ['company', 'product'] },
],
properties: {
id: {
type: BigIntType,
autoincrement: true,
primary: true,
},
product: {
reference: 'm:1',
entity: () => ProductEntity,
},
company: {
reference: 'm:1',
entity: () => Company,
},
createdAt: {
type: 'timestamp',
onCreate: () => new Date(),
defaultRaw: 'current_timestamp',
},
updatedAt: {
type: 'timestamp',
onUpdate: () => new Date(),
defaultRaw: 'current_timestamp',
},
},
});

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
schema: 'test',
dbName: '4988',
entities: [companySchema, productSchema, companyProductsSchema],
});

await orm.getSchemaGenerator().refreshDatabase();
});

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

test('creates a company with products', async () => {
const company = new Company({
name: 'Acme',
});
await orm.em.persistAndFlush(company);

const product = new ProductEntity({ name: 'ProductA' });
company.products.add(product);
await orm.em.flush();

expect(company.products).toHaveLength(1);
});

0 comments on commit e250634

Please sign in to comment.