Skip to content

Commit

Permalink
fix(core): allow hydration of non persistent embedded properties (#5579)
Browse files Browse the repository at this point in the history
Closes #5578

---------

Co-authored-by: Martin Adámek <banan23@gmail.com>
  • Loading branch information
toonvanstrijp and B4nan committed May 20, 2024
1 parent a132036 commit e8c0c3f
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/core/src/decorators/Embedded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ export type EmbeddedOptions = {
serializer?: (value: any) => any;
serializedName?: string;
groups?: string[];
persist?: boolean;
};
4 changes: 4 additions & 0 deletions packages/core/src/hydration/ObjectHydrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export class ObjectHydrator extends Hydrator {
const idx = this.tmpIndex++;
const nullVal = this.config.get('forceUndefined') ? 'undefined' : 'null';

if (prop.getter && !prop.setter) {
return [];
}

if (prop.ref) {
context.set('ScalarReference', ScalarReference);
ret.push(` const oldValue_${idx} = entity${entityKey};`);
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/metadata/MetadataDiscovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -962,14 +962,15 @@ export class MetadataDiscovery {
this.initFieldName(embeddedProp, rootProperty !== embeddedProp && object);
const prefix = embeddedProp.prefix === false ? '' : embeddedProp.prefix === true ? embeddedProp.embeddedPath?.join('_') ?? embeddedProp.fieldNames[0] + '_' : embeddedProp.prefix;

for (const prop of Object.values(embeddable.properties).filter(p => p.persist !== false)) {
for (const prop of Object.values(embeddable.properties)) {
const name = (embeddedProp.embeddedPath?.join('_') ?? embeddedProp.fieldNames[0] + '_') + prop.name;

meta.properties[name] = Utils.copy(prop, false);
meta.properties[name].name = name;
meta.properties[name].embedded = [embeddedProp.name, prop.name];
meta.propertyOrder.set(name, (order += 0.01));
embeddedProp.embeddedProps[prop.name] = meta.properties[name];
meta.properties[name].persist ??= embeddedProp.persist;

if (embeddedProp.nullable) {
meta.properties[name].nullable = true;
Expand Down
79 changes: 79 additions & 0 deletions tests/features/embeddables/GH5578.postgres.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Collection, Embeddable, Embedded, Entity, ManyToOne, MikroORM, OneToMany, PrimaryKey, Property, raw } from '@mikro-orm/postgresql';


@Embeddable()
class Statistic {

@Property()
revenue!: number;

}

@Entity()
class Event {

@PrimaryKey()
id!: number;

@Property()
name!: string;

@OneToMany({ entity: () => Order, mappedBy: order => order.event })
orders = new Collection<Order>(this);

@Embedded({ entity: () => Statistic, nullable: true, prefix: false, persist: false })
statistic?: Statistic;

}

@Entity()
class Order {

@PrimaryKey()
id!: number;

@Property()
total!: number;

@ManyToOne(() => Event, { nullable: true })
event!: Event;

}

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
entities: [Order, Event],
dbName: '5578',
});
await orm.schema.refreshDatabase();
});

beforeEach(() => orm.schema.clearDatabase());
afterAll(() => orm.close(true));

test('Hydrate non persistent properties on embeddable', async () => {
const eventFoo = orm.em.create(Event, { id: 1, name: 'Foo' });
const eventBar = orm.em.create(Event, { id: 2, name: 'Bar' });
orm.em.create(Order, { id: 1, total: 100, event: eventFoo });
orm.em.create(Order, { id: 2, total: 150, event: eventFoo });
orm.em.create(Order, { id: 3, total: 40, event: eventBar });
orm.em.create(Order, { id: 4, total: 20, event: eventBar });
await orm.em.flush();
orm.em.clear();

const qb = orm.em.createQueryBuilder(Event, 'e');

const results = await qb
.select([
'e.*',
raw('sum(o.total) as revenue'),
])
.leftJoin('e.orders', 'o')
.groupBy('e.id')
.getResult();

expect(results.find(e => e.name === 'Foo')?.statistic?.revenue).toBe(250);
expect(results.find(e => e.name === 'Bar')?.statistic?.revenue).toBe(60);
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`embedded entities with custom types schema: embeddables custom types 1 1`] = `
"create table "parent" ("id" serial primary key, "nested_some_value" varchar not null, "nested_deep_some_value" varchar not null, "nested2" jsonb not null, "after" int null, "some_value" varchar null);
create table "user" ("id" serial primary key, "savings_amount" numeric(14,2) not null, "after" int null);
create table "user" ("id" serial primary key, "savings_amount" numeric(14,2) not null, "views" double precision null, "after" int null);
"
`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { EntityProperty, Platform } from '@mikro-orm/core';
import { Embeddable, Embedded, Entity, MikroORM, PrimaryKey, Property, Type } from '@mikro-orm/core';
import { DoubleType, Embeddable, Embedded, Entity, MikroORM, PrimaryKey, Property, Type } from '@mikro-orm/core';
import { PostgreSqlDriver } from '@mikro-orm/postgresql';
import { mockLogger } from '../../helpers';

Expand Down Expand Up @@ -95,6 +95,21 @@ class Savings {

}

@Embeddable()
class Statistic {

@Property({ type: DoubleType })
total: number;

@Property({ type: DoubleType, persist: true })
views!: number;

constructor(total: number) {
this.total = total;
}

}

@Entity()
class User {

Expand All @@ -104,6 +119,9 @@ class User {
@Embedded(() => Savings)
savings!: Savings;

@Embedded(() => Statistic, { prefix: false, nullable: true, persist: false })
statistic?: Statistic;

@Property({ nullable: true })
after?: number; // property after embeddables to verify order props in resulting schema

Expand Down

0 comments on commit e8c0c3f

Please sign in to comment.